Merge "Release the NOTIFICATION_GROUP_DISMISSAL_ANIMATION flag" into tm-qpr-dev
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 93c0c4d..d3b63a8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -797,6 +797,7 @@
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
     field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
+    field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
     field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
   }
 
@@ -2411,6 +2412,7 @@
   public abstract class DreamOverlayService extends android.app.Service {
     ctor public DreamOverlayService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public void onEndDream();
     method public abstract void onStartDream(@NonNull android.view.WindowManager.LayoutParams);
     method public final void requestExit();
     method public final boolean shouldShowComplications();
@@ -2916,7 +2918,9 @@
   }
 
   @UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+    method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
     method public android.view.View getTooltipView();
+    method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
     method public boolean isAutofilled();
     method public static boolean isDefaultFocusHighlightEnabled();
     method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8b41aa4..46d8481 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7944,8 +7944,6 @@
          * @hide
          */
         public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
-            // TODO(b/228941516): This icon should be downscaled to avoid using too much memory,
-            // see reduceImageSizes.
             mShortcutIcon = conversationIcon;
             return this;
         }
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index a51b9d3..27f9f54 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -1406,6 +1406,17 @@
     }
 
     /**
+     * Return the number of entries in the cache.  This is used for testing and has package-only
+     * visibility.
+     * @hide
+     */
+    public int size() {
+        synchronized (mLock) {
+            return mCache.size();
+        }
+    }
+
+    /**
      * Returns a list of caches alive at the current time.
      */
     @GuardedBy("sGlobalLock")
@@ -1612,8 +1623,12 @@
      * @hide
      */
     public static void onTrimMemory() {
-        for (PropertyInvalidatedCache pic : getActiveCaches()) {
-            pic.clear();
+        ArrayList<PropertyInvalidatedCache> activeCaches;
+        synchronized (sGlobalLock) {
+            activeCaches = getActiveCaches();
+        }
+        for (int i = 0; i < activeCaches.size(); i++) {
+            activeCaches.get(i).clear();
         }
     }
 }
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 067a4c3..a34a50c 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -27,6 +27,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Size;
@@ -101,11 +102,13 @@
     // Decides when dark theme is optimal for this wallpaper
     private static final float DARK_THEME_MEAN_LUMINANCE = 0.3f;
     // Minimum mean luminosity that an image needs to have to support dark text
-    private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.7f;
+    private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = SystemProperties.getInt(
+            "persist.wallpapercolors.threshold", 70) / 100f;
     // We also check if the image has dark pixels in it,
     // to avoid bright images with some dark spots.
     private static final float DARK_PIXEL_CONTRAST = 5.5f;
-    private static final float MAX_DARK_AREA = 0.05f;
+    private static final float MAX_DARK_AREA = SystemProperties.getInt(
+            "persist.wallpapercolors.max_dark_area", 5) / 100f;
 
     private final List<Color> mMainColors;
     private final Map<Integer, Integer> mAllColors;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a320f1e..3daee1f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5737,7 +5737,7 @@
      * @hide
      */
     public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS =
-            "android.intent.extra.EXTRA_CHOOSER_CUSTOM_ACTIONS";
+            "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
 
     /**
      * Optional argument to be used with {@link #ACTION_CHOOSER}.
@@ -5748,7 +5748,7 @@
      * @hide
      */
     public static final String EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION =
-            "android.intent.extra.EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION";
+            "android.intent.extra.CHOOSER_PAYLOAD_RESELECTION_ACTION";
 
     /**
      * An {@code ArrayList} of {@code String} annotations describing content for
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 2e3b5d2..226278c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1058,6 +1058,17 @@
     public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id
 
     /**
+     * This change id excludes the packages it is applied to from ignoreOrientationRequest behaviour
+     * that can be enabled by the device manufacturers for the com.android.server.wm.DisplayArea
+     * or for the whole display.
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @Disabled
+    public static final long OVERRIDE_RESPECT_REQUESTED_ORIENTATION = 236283604L; // buganizer id
+
+    /**
      * This change id excludes the packages it is applied to from the camera compat force rotation
      * treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context.
      * @hide
@@ -1093,6 +1104,34 @@
             264301586L; // buganizer id
 
     /**
+     * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
+     * an activity bounds for:
+     *
+     * <p>{@link android.view.View#getLocationOnScreen},
+     * {@link android.view.View#getWindowVisibleDisplayFrame},
+     * {@link android.view.View}#getWindowDisplayFrame,
+     * {@link android.view.View}#getBoundsOnScreen.
+     *
+     * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
+     * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
+     * through
+     * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
+     * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+     *
+     * <p>Some applications assume that they occupy the whole screen and therefore use the display
+     * coordinates in their calculations as if an activity is  positioned in the top-left corner of
+     * the screen, with left coordinate equal to 0. This may not be the case of applications in
+     * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
+     * case the activity is Letterboxed or is in multi-window mode.
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @Disabled
+    @TestApi
+    public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
+
+    /**
      * This change id is the gatekeeper for all treatments that force a given min aspect ratio.
      * Enabling this change will allow the following min aspect ratio treatments to be applied:
      * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
@@ -1187,6 +1226,91 @@
     @Overridable
     public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L;
 
+    // Compat framework that per-app overrides rely on only supports booleans. That's why we have
+    // multiple OVERRIDE_*_ORIENTATION_* change ids below instead of just one override with
+    // the integer value.
+
+    /**
+     * Enables {@link #SCREEN_ORIENTATION_PORTRAIT}. Unless OVERRIDE_ANY_ORIENTATION
+     * is enabled, this override is used only when no other fixed orientation was specified by the
+     * activity.
+     * @hide
+     */
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L;
+
+    /**
+     * Enables {@link #SCREEN_ORIENTATION_NOSENSOR}. Unless OVERRIDE_ANY_ORIENTATION
+     * is enabled, this override is used only when no other fixed orientation was specified by the
+     * activity.
+     * @hide
+     */
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR = 265451093L;
+
+    /**
+     * Enables {@link #SCREEN_ORIENTATION_REVERSE_LANDSCAPE}. Unless OVERRIDE_ANY_ORIENTATION
+     * is enabled, this override is used only when activity specify landscape orientation.
+     * This can help apps that assume that landscape display orientation corresponds to {@link
+     * android.view.Surface#ROTATION_90}, while on some devices it can be {@link
+     * android.view.Surface#ROTATION_270}.
+     * @hide
+     */
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L;
+
+    /**
+     * When enabled, allows OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE,
+     * OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR and OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
+     * to override any orientation requested by the activity.
+     * @hide
+     */
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long OVERRIDE_ANY_ORIENTATION = 265464455L;
+
+    /**
+     * When enabled, activates OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE,
+     * OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR and OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
+     * only when an app is connected to the camera. See
+     * com.android.server.wm.DisplayRotationCompatPolicy for more context.
+     * @hide
+     */
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA = 265456536L;
+
+    /**
+     * This override fixes display orientation to landscape natural orientation when a task is
+     * fullscreen. While display rotation is fixed to landscape, the orientation requested by the
+     * activity will be still respected by bounds resolution logic. For instance, if an activity
+     * requests portrait orientation and this override is set, then activity will appear in the
+     * letterbox mode for fixed orientation with the display rotated to the lanscape natural
+     * orientation.
+     *
+     * <p>This override is applicable only when natural orientation of the device is
+     * landscape and display ignores orientation requestes.
+     *
+     * <p>Main use case for this override are camera-using activities that are portrait-only and
+     * assume alignment with natural device orientation. Such activities can automatically be
+     * rotated with com.android.server.wm.DisplayRotationCompatPolicy but not all of them can
+     * handle dynamic rotation and thus can benefit from this override.
+     *
+     * @hide
+     */
+    @ChangeId
+    @Disabled
+    @Overridable
+    public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L;
+
     /**
      * Compares activity window layout min width/height with require space for multi window to
      * determine if it can be put into multi window mode.
@@ -1405,8 +1529,19 @@
      * @hide
      */
     public boolean isFixedOrientation() {
-        return isFixedOrientationLandscape() || isFixedOrientationPortrait()
-                || screenOrientation == SCREEN_ORIENTATION_LOCKED;
+        return isFixedOrientation(screenOrientation);
+    }
+
+    /**
+     * Returns true if the passed activity's orientation is fixed.
+     * @hide
+     */
+    public static boolean isFixedOrientation(@ScreenOrientation int orientation) {
+        return orientation == SCREEN_ORIENTATION_LOCKED
+                // Orientation is fixed to natural display orientation
+                || orientation == SCREEN_ORIENTATION_NOSENSOR
+                || isFixedOrientationLandscape(orientation)
+                || isFixedOrientationPortrait(orientation);
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index df1c0d7..e5243ee 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -878,8 +878,8 @@
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code VIDEO_CALL}</td> <td colspan="3" id="rb"></td> <td>Preview with video call</td> </tr>
      * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>Multi-purpose stream with JPEG or YUV still capture</td> </tr>
      * <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>YUV and JPEG concurrent still image capture (for testing)</td> </tr>
-     * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG or YUV video snapshot</td> </tr>
-     * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG or YUV still image capture</td> </tr>
+     * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG video snapshot</td> </tr>
+     * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG still image capture</td> </tr>
      * </table><br>
      * </p>
      *
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 4dc6e93..32cf0a7 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -2353,6 +2353,15 @@
                 final AvailabilityCallback callback = mCallbackMap.keyAt(i);
 
                 postSingleUpdate(callback, executor, id, null /*physicalId*/, status);
+
+                // Send the NOT_PRESENT state for unavailable physical cameras
+                if (isAvailable(status) && mUnavailablePhysicalDevices.containsKey(id)) {
+                    ArrayList<String> unavailableIds = mUnavailablePhysicalDevices.get(id);
+                    for (String unavailableId : unavailableIds) {
+                        postSingleUpdate(callback, executor, id, unavailableId,
+                                ICameraServiceListener.STATUS_NOT_PRESENT);
+                    }
+                }
             }
         } // onStatusChangedLocked
 
@@ -2372,9 +2381,8 @@
             }
 
             //TODO: Do we need to treat this as error?
-            if (!mDeviceStatus.containsKey(id) || !isAvailable(mDeviceStatus.get(id))
-                    || !mUnavailablePhysicalDevices.containsKey(id)) {
-                Log.e(TAG, String.format("Camera %s is not available. Ignore physical camera "
+            if (!mDeviceStatus.containsKey(id) || !mUnavailablePhysicalDevices.containsKey(id)) {
+                Log.e(TAG, String.format("Camera %s is not present. Ignore physical camera "
                         + "status change", id));
                 return;
             }
@@ -2399,6 +2407,12 @@
                 return;
             }
 
+            if (!isAvailable(mDeviceStatus.get(id))) {
+                Log.i(TAG, String.format("Camera %s is not available. Ignore physical camera "
+                        + "status change callback(s)", id));
+                return;
+            }
+
             final int callbackCount = mCallbackMap.size();
             for (int i = 0; i < callbackCount; i++) {
                 Executor executor = mCallbackMap.valueAt(i);
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index e5b9cdb..754472f 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1223,14 +1223,6 @@
         new StreamCombinationTemplate(new StreamTemplate [] {
                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
                         STREAM_USE_CASE_PREVIEW),
-                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.RECORD,
-                        STREAM_USE_CASE_RECORD),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
-                        STREAM_USE_CASE_STILL_CAPTURE)},
-                "Preview, video record and YUV video snapshot"),
-        new StreamCombinationTemplate(new StreamTemplate [] {
-                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
-                        STREAM_USE_CASE_PREVIEW),
                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
                         STREAM_USE_CASE_RECORD),
                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.RECORD,
@@ -1239,27 +1231,11 @@
         new StreamCombinationTemplate(new StreamTemplate [] {
                 new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
                         STREAM_USE_CASE_PREVIEW),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
-                        STREAM_USE_CASE_RECORD),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.RECORD,
-                        STREAM_USE_CASE_STILL_CAPTURE)},
-                "Preview, in-application video processing and YUV video snapshot"),
-        new StreamCombinationTemplate(new StreamTemplate [] {
-                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
-                        STREAM_USE_CASE_PREVIEW),
                 new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
                         STREAM_USE_CASE_PREVIEW),
                 new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM,
                         STREAM_USE_CASE_STILL_CAPTURE)},
                 "Preview, in-application image processing, and JPEG still image capture"),
-        new StreamCombinationTemplate(new StreamTemplate [] {
-                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
-                        STREAM_USE_CASE_PREVIEW),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
-                        STREAM_USE_CASE_PREVIEW),
-                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM,
-                        STREAM_USE_CASE_STILL_CAPTURE)},
-                "Preview, in-application image processing, and YUV still image capture"),
     };
 
     private static StreamCombinationTemplate sPreviewStabilizedStreamCombinations[] = {
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
index 9c2aa66..a36ccf6 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
@@ -39,5 +39,15 @@
      *        {@link android.view.Display#getDisplayId()}.
      */
     void onHbmDisabled(int displayId);
+
+    /**
+     * To avoid delay in switching refresh rate when activating LHBM, allow screens to request
+     * higher refersh rate if auth is possible on particular screen
+     *
+     * @param displayId The displayId for which the refresh rate should be unset. See
+     *        {@link android.view.Display#getDisplayId()}.
+     * @param isPossible If authentication is possible on particualr screen
+     */
+    void onAuthenticationPossible(int displayId, boolean isPossible);
 }
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index baf8836..b6cd06e 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -168,7 +168,7 @@
      * The <code>android:label</code> attribute specifies a human-readable descriptive
      * label to describe the keyboard layout in the user interface, such as "English (US)".
      * The <code>android:keyboardLayout</code> attribute refers to a
-     * <a href="http://source.android.com/tech/input/key-character-map-files.html">
+     * <a href="https://source.android.com/docs/core/interaction/input/key-character-map-files">
      * key character map</a> resource that defines the keyboard layout.
      * </p>
      */
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 6330661..1929a4d 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -536,8 +536,8 @@
             mWarnOnBlocking = false;
             warnOnBlocking = false;
 
-            if (Build.IS_USERDEBUG) {
-                // Log this as a WTF on userdebug builds.
+            if (Build.IS_USERDEBUG || Build.IS_ENG) {
+                // Log this as a WTF on userdebug and eng builds.
                 Log.wtf(Binder.TAG,
                         "Outgoing transactions from this process must be FLAG_ONEWAY",
                         new Throwable());
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index ac2156e..ca34337 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -109,7 +109,8 @@
     public static final long TRACE_TAG_THERMAL = 1L << 27;
 
     private static final long TRACE_TAG_NOT_READY = 1L << 63;
-    private static final int MAX_SECTION_NAME_LEN = 127;
+    /** @hide **/
+    public static final int MAX_SECTION_NAME_LEN = 127;
 
     // Must be volatile to avoid word tearing.
     // This is only kept in case any apps get this by reflection but do not
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 497bfa6..d9604b3f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2555,6 +2555,8 @@
      * This API doesn't require any special permissions, though typical implementations
      * will require being called from an SELinux domain that allows setting file attributes
      * related to quota (eg the GID or project ID).
+     * If the calling user has MANAGE_EXTERNAL_STORAGE permissions, quota for shared profile's
+     * volumes is also updated.
      *
      * The default platform user of this API is the MediaProvider process, which is
      * responsible for managing all of external storage.
@@ -2575,7 +2577,14 @@
             @QuotaType int quotaType) throws IOException {
         long projectId;
         final String filePath = path.getCanonicalPath();
-        final StorageVolume volume = getStorageVolume(path);
+        int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE;
+        // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are also
+        // returned by enabling FLAG_INCLUDE_SHARED_PROFILE.
+        if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
+            volFlags |= FLAG_INCLUDE_SHARED_PROFILE;
+        }
+        final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags);
+        final StorageVolume volume = getStorageVolume(availableVolumes, path);
         if (volume == null) {
             Log.w(TAG, "Failed to update quota type for " + filePath);
             return;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a1a1c45..ce4a735 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9534,7 +9534,7 @@
         /**
          * Indicates whether "seen" notifications should be suppressed from the lockscreen.
          * <p>
-         * Type: int (0 for false, 1 for true)
+         * Type: int (0 for unset, 1 for true, 2 for false)
          *
          * @hide
          */
@@ -9838,9 +9838,11 @@
 
         /**
          * Whether or not a SFPS device is enabling the performant auth setting.
+         * The "_V2" suffix was added to re-introduce the default behavior for
+         * users. See b/265264294 fore more details.
          * @hide
          */
-        public static final String SFPS_PERFORMANT_AUTH_ENABLED = "sfps_performant_auth_enabled";
+        public static final String SFPS_PERFORMANT_AUTH_ENABLED = "sfps_performant_auth_enabled_v2";
 
         /**
          * Whether or not debugging is enabled.
diff --git a/core/java/android/service/chooser/ChooserAction.java b/core/java/android/service/chooser/ChooserAction.java
index 3010049..a61b781 100644
--- a/core/java/android/service/chooser/ChooserAction.java
+++ b/core/java/android/service/chooser/ChooserAction.java
@@ -27,10 +27,9 @@
 
 /**
  * A ChooserAction is an app-defined action that can be provided to the Android Sharesheet to
- * be shown to the user when {@link android.content.Intent.ACTION_CHOOSER} is invoked.
+ * be shown to the user when {@link android.content.Intent#ACTION_CHOOSER} is invoked.
  *
- * @see android.content.Intent.EXTRA_CHOOSER_CUSTOM_ACTIONS
- * @see android.content.Intent.EXTRA_CHOOSER_PAYLOAD_RESELECTION_ACTION
+ * @see android.content.Intent#EXTRA_CHOOSER_CUSTOM_ACTIONS
  * @hide
  */
 public final class ChooserAction implements Parcelable {
@@ -88,6 +87,7 @@
         return "ChooserAction {" + "label=" + mLabel + ", intent=" + mAction + "}";
     }
 
+    @NonNull
     public static final Parcelable.Creator<ChooserAction> CREATOR =
             new Creator<ChooserAction>() {
                 @Override
@@ -137,6 +137,7 @@
          * object.
          * @return the built action
          */
+        @NonNull
         public ChooserAction build() {
             return new ChooserAction(mIcon, mLabel, mAction);
         }
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index 6e8198b..6e4535b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -36,33 +36,100 @@
 public abstract class DreamOverlayService extends Service {
     private static final String TAG = "DreamOverlayService";
     private static final boolean DEBUG = false;
-    private boolean mShowComplications;
-    private ComponentName mDreamComponent;
 
-    private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+    // The last client that started dreaming and hasn't ended
+    private OverlayClient mCurrentClient;
+
+    // An {@link IDreamOverlayClient} implementation that identifies itself when forwarding
+    // requests to the {@link DreamOverlayService}
+    private static class OverlayClient extends IDreamOverlayClient.Stub {
+        private final DreamOverlayService mService;
+        private boolean mShowComplications;
+        private ComponentName mDreamComponent;
+        IDreamOverlayCallback mDreamOverlayCallback;
+
+        OverlayClient(DreamOverlayService service) {
+            mService = service;
+        }
+
         @Override
-        public void startDream(WindowManager.LayoutParams layoutParams,
-                IDreamOverlayCallback callback, String dreamComponent,
-                boolean shouldShowComplications) {
-            mDreamOverlayCallback = callback;
+        public void startDream(WindowManager.LayoutParams params, IDreamOverlayCallback callback,
+                String dreamComponent, boolean shouldShowComplications) throws RemoteException {
             mDreamComponent = ComponentName.unflattenFromString(dreamComponent);
             mShowComplications = shouldShowComplications;
-            onStartDream(layoutParams);
+            mDreamOverlayCallback = callback;
+            mService.startDream(this, params);
         }
 
+
+
         @Override
         public void wakeUp() {
-            onWakeUp(() -> {
+            mService.wakeUp(this, () -> {
                 try {
                     mDreamOverlayCallback.onWakeUpComplete();
                 } catch (RemoteException e) {
-                    Log.e(TAG, "Could not notify dream of wakeUp:" + e);
+                    Log.e(TAG, "Could not notify dream of wakeUp", e);
                 }
             });
         }
-    };
 
-    IDreamOverlayCallback mDreamOverlayCallback;
+        @Override
+        public void endDream() {
+            mService.endDream(this);
+        }
+
+        private void onExitRequested() {
+            try {
+                mDreamOverlayCallback.onExitRequested();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not request exit:" + e);
+            }
+        }
+
+        private boolean shouldShowComplications() {
+            return mShowComplications;
+        }
+
+        private ComponentName getComponent() {
+            return mDreamComponent;
+        }
+    }
+
+    private void startDream(OverlayClient client, WindowManager.LayoutParams params) {
+        endDream(mCurrentClient);
+        mCurrentClient = client;
+        onStartDream(params);
+    }
+
+    private void endDream(OverlayClient client) {
+        if (client == null || client != mCurrentClient) {
+            return;
+        }
+
+        onEndDream();
+        mCurrentClient = null;
+    }
+
+    private void wakeUp(OverlayClient client, Runnable callback) {
+        if (mCurrentClient != client) {
+            return;
+        }
+
+        onWakeUp(callback);
+    }
+
+    private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+        @Override
+        public void getClient(IDreamOverlayClientCallback callback) {
+            try {
+                callback.onDreamOverlayClient(
+                        new OverlayClient(DreamOverlayService.this));
+            } catch (RemoteException e) {
+                Log.e(TAG, "could not send client to callback", e);
+            }
+        }
+    };
 
     public DreamOverlayService() {
     }
@@ -83,31 +150,45 @@
 
     /**
      * This method is overridden by implementations to handle when the dream has been requested
-     * to wakeup. This allows any overlay animations to run.
+     * to wakeup. This allows any overlay animations to run. By default, the method will invoke
+     * the callback immediately.
      *
      * @param onCompleteCallback The callback to trigger to notify the dream service that the
      *                           overlay has completed waking up.
      * @hide
      */
     public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+        onCompleteCallback.run();
+    }
+
+    /**
+     * This method is overridden by implementations to handle when the dream has ended. There may
+     * be earlier signals leading up to this step, such as @{@link #onWakeUp(Runnable)}.
+     */
+    public void onEndDream() {
     }
 
     /**
      * This method is invoked to request the dream exit.
      */
     public final void requestExit() {
-        try {
-            mDreamOverlayCallback.onExitRequested();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not request exit:" + e);
+        if (mCurrentClient == null) {
+            throw new IllegalStateException("requested exit with no dream present");
         }
+
+        mCurrentClient.onExitRequested();
     }
 
     /**
      * Returns whether to show complications on the dream overlay.
      */
     public final boolean shouldShowComplications() {
-        return mShowComplications;
+        if (mCurrentClient == null) {
+            throw new IllegalStateException(
+                    "requested if should show complication when no dream active");
+        }
+
+        return mCurrentClient.shouldShowComplications();
     }
 
     /**
@@ -115,6 +196,10 @@
      * @hide
      */
     public final ComponentName getDreamComponent() {
-        return mDreamComponent;
+        if (mCurrentClient == null) {
+            throw new IllegalStateException("requested dream component when no dream active");
+        }
+
+        return mCurrentClient.getComponent();
     }
 }
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 8b9852a..6a4710f 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -248,25 +248,39 @@
     private OverlayConnection mOverlayConnection;
 
     private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> {
-        // Overlay set during onBind.
-        private IDreamOverlay mOverlay;
+        // Retrieved Client
+        private IDreamOverlayClient mClient;
+
         // A list of pending requests to execute on the overlay.
-        private final ArrayList<Consumer<IDreamOverlay>> mConsumers = new ArrayList<>();
+        private final ArrayList<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>();
+
+        private final IDreamOverlayClientCallback mClientCallback =
+                new IDreamOverlayClientCallback.Stub() {
+            @Override
+            public void onDreamOverlayClient(IDreamOverlayClient client) {
+                mClient = client;
+
+                for (Consumer<IDreamOverlayClient> consumer : mConsumers) {
+                    consumer.accept(mClient);
+                }
+            }
+        };
 
         private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() {
             @Override
             public void onConnected(ObservableServiceConnection<IDreamOverlay> connection,
                     IDreamOverlay service) {
-                mOverlay = service;
-                for (Consumer<IDreamOverlay> consumer : mConsumers) {
-                    consumer.accept(mOverlay);
+                try {
+                    service.getClient(mClientCallback);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "could not get DreamOverlayClient", e);
                 }
             }
 
             @Override
             public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection,
                     int reason) {
-                mOverlay = null;
+                mClient = null;
             }
         };
 
@@ -296,15 +310,21 @@
             super.unbind();
         }
 
-        public void addConsumer(Consumer<IDreamOverlay> consumer) {
-            mConsumers.add(consumer);
-            if (mOverlay != null) {
-                consumer.accept(mOverlay);
-            }
+        public void addConsumer(Consumer<IDreamOverlayClient> consumer) {
+            execute(() -> {
+                mConsumers.add(consumer);
+                if (mClient != null) {
+                    consumer.accept(mClient);
+                }
+            });
         }
 
-        public void removeConsumer(Consumer<IDreamOverlay> consumer) {
-            mConsumers.remove(consumer);
+        public void removeConsumer(Consumer<IDreamOverlayClient> consumer) {
+            execute(() -> mConsumers.remove(consumer));
+        }
+
+        public void clearConsumers() {
+            execute(() -> mConsumers.clear());
         }
     }
 
@@ -1044,6 +1064,24 @@
      * </p>
      */
     public final void finish() {
+        // If there is an active overlay connection, signal that the dream is ending before
+        // continuing. Note that the overlay cannot rely on the unbound state, since another dream
+        // might have bound to it in the meantime.
+        if (mOverlayConnection != null) {
+            mOverlayConnection.addConsumer(overlay -> {
+                try {
+                    overlay.endDream();
+                    mOverlayConnection.unbind();
+                    mOverlayConnection = null;
+                    finish();
+                } catch (RemoteException e) {
+                    Log.e(mTag, "could not inform overlay of dream end:" + e);
+                }
+            });
+            mOverlayConnection.clearConsumers();
+            return;
+        }
+
         if (mDebug) Slog.v(mTag, "finish(): mFinished=" + mFinished);
 
         Activity activity = mActivity;
@@ -1060,10 +1098,6 @@
         }
         mFinished = true;
 
-        if (mOverlayConnection != null) {
-            mOverlayConnection.unbind();
-        }
-
         if (mDreamToken == null) {
             if (mDebug) Slog.v(mTag, "finish() called when not attached.");
             stopSelf();
@@ -1359,7 +1393,7 @@
 
         mWindow.getDecorView().addOnAttachStateChangeListener(
                 new View.OnAttachStateChangeListener() {
-                    private Consumer<IDreamOverlay> mDreamStartOverlayConsumer;
+                    private Consumer<IDreamOverlayClient> mDreamStartOverlayConsumer;
 
                     @Override
                     public void onViewAttachedToWindow(View v) {
@@ -1391,6 +1425,7 @@
                             mActivity = null;
                             finish();
                         }
+
                         if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
                             mOverlayConnection.removeConsumer(mDreamStartOverlayConsumer);
                         }
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 7aeceb2c..7ec75a5 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -16,8 +16,7 @@
 
 package android.service.dreams;
 
-import android.service.dreams.IDreamOverlayCallback;
-import android.view.WindowManager.LayoutParams;
+import android.service.dreams.IDreamOverlayClientCallback;
 
 /**
 * {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view
@@ -28,17 +27,7 @@
 */
 interface IDreamOverlay {
     /**
-    * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
-                    token of the Dream Activity.
-    * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
-    *                dream.
-    * @param dreamComponent The component name of the dream service requesting overlay.
-    * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
-    *                and weather.
+    * Retrieves a client the caller can use to interact with the dream overlay.
     */
-    void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
-        in String dreamComponent, in boolean shouldShowComplications);
-
-    /** Called when the dream is waking, to do any exit animations */
-    void wakeUp();
+    void getClient(in IDreamOverlayClientCallback callback);
 }
diff --git a/core/java/android/service/dreams/IDreamOverlayClient.aidl b/core/java/android/service/dreams/IDreamOverlayClient.aidl
new file mode 100644
index 0000000..78b7280
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlayClient.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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 android.service.dreams;
+
+import android.service.dreams.IDreamOverlayCallback;
+import android.view.WindowManager.LayoutParams;
+
+/**
+* {@link IDreamOverlayClient} allows {@link DreamService} instances to act upon the dream overlay.
+*
+* @hide
+*/
+interface IDreamOverlayClient {
+    /**
+    * @param params The {@link LayoutParams} for the associated DreamWindow, including the window
+                    token of the Dream Activity.
+    * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
+    *                dream.
+    * @param dreamComponent The component name of the dream service requesting overlay.
+    * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock
+    *                and weather.
+    */
+    void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
+        in String dreamComponent, in boolean shouldShowComplications);
+
+    /** Called when the dream is waking, to do any exit animations */
+    void wakeUp();
+
+    /** Called when the dream has ended. */
+    void endDream();
+}
diff --git a/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl
new file mode 100644
index 0000000..244d999
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamOverlayClientCallback.aidl
@@ -0,0 +1,30 @@
+/**
+ * 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 android.service.dreams;
+
+import android.service.dreams.IDreamOverlayClient;
+
+/**
+* {@link IDreamOverlayClientCallback} allows receiving a requested {@link IDreamOverlayClient}.
+* @hide
+*/
+interface IDreamOverlayClientCallback {
+    /**
+    * Called with a unique {@link IDreamOverlayClient}.
+    */
+    void onDreamOverlayClient(in IDreamOverlayClient client);
+}
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index b09d2e9..7aacb9b 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -16,6 +16,7 @@
 
 package android.service.quickaccesswallet;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -24,28 +25,73 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
 /**
  * A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
  * card, library card, transit pass, etc. Cards are identified by a String identifier and contain a
- * card image, card image content description, and a {@link PendingIntent} to be used if the user
- * clicks on the card. Cards may be displayed with an icon and label, though these are optional.
+ * card type, card image, card image content description, and a {@link PendingIntent} to be used if
+ * the user clicks on the card. Cards may be displayed with an icon and label, though these are
+ * optional. Valuable cards will also have a second image that will be displayed when the card is
+ * tapped.
  */
+
 public final class WalletCard implements Parcelable {
 
+    /**
+     * Unknown cards refer to cards whose types are unspecified.
+     * @hide
+     */
+    public static final int CARD_TYPE_UNKNOWN = 0;
+
+    /**
+     * Payment cards refer to credit cards, debit cards or any other cards in the wallet used to
+     * make cash-equivalent payments.
+     * @hide
+     */
+    public static final int CARD_TYPE_PAYMENT = 1;
+
+    /**
+     * Valuable cards refer to any cards that are not used for cash-equivalent payment.
+     * This includes event tickets, flights, offers, loyalty cards, gift cards and transit tickets.
+     * @hide
+     */
+    public static final int CARD_TYPE_VALUABLE = 2;
+
     private final String mCardId;
+    private final int mCardType;
     private final Icon mCardImage;
     private final CharSequence mContentDescription;
     private final PendingIntent mPendingIntent;
     private final Icon mCardIcon;
     private final CharSequence mCardLabel;
+    private final Icon mValuableCardSecondaryImage;
 
     private WalletCard(Builder builder) {
         this.mCardId = builder.mCardId;
+        this.mCardType = builder.mCardType;
         this.mCardImage = builder.mCardImage;
         this.mContentDescription = builder.mContentDescription;
         this.mPendingIntent = builder.mPendingIntent;
         this.mCardIcon = builder.mCardIcon;
         this.mCardLabel = builder.mCardLabel;
+        this.mValuableCardSecondaryImage = builder.mValuableCardSecondaryImage;
+    }
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CARD_TYPE_"}, value = {
+            CARD_TYPE_UNKNOWN,
+            CARD_TYPE_PAYMENT,
+            CARD_TYPE_VALUABLE
+    })
+    public @interface CardType {
     }
 
     @Override
@@ -56,29 +102,44 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mCardId);
+        dest.writeInt(mCardType);
         mCardImage.writeToParcel(dest, flags);
         TextUtils.writeToParcel(mContentDescription, dest, flags);
         PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
-        if (mCardIcon == null) {
+        writeIconIfNonNull(mCardIcon, dest, flags);
+        TextUtils.writeToParcel(mCardLabel, dest, flags);
+        writeIconIfNonNull(mValuableCardSecondaryImage, dest, flags);
+
+    }
+
+    /** Utility function called by writeToParcel
+     */
+    private void writeIconIfNonNull(Icon icon,  Parcel dest, int flags) {
+        if (icon == null) {
             dest.writeByte((byte) 0);
         } else {
             dest.writeByte((byte) 1);
-            mCardIcon.writeToParcel(dest, flags);
+            icon.writeToParcel(dest, flags);
         }
-        TextUtils.writeToParcel(mCardLabel, dest, flags);
     }
 
     private static WalletCard readFromParcel(Parcel source) {
         String cardId = source.readString();
+        int cardType = source.readInt();
         Icon cardImage = Icon.CREATOR.createFromParcel(source);
         CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
         PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
         Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
         CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-        return new Builder(cardId, cardImage, contentDesc, pendingIntent)
+        Icon valuableCardSecondaryImage = source.readByte() == 0 ? null :
+                Icon.CREATOR.createFromParcel(source);
+        Builder builder = new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
                 .setCardIcon(cardIcon)
-                .setCardLabel(cardLabel)
-                .build();
+                .setCardLabel(cardLabel);
+
+        return cardType == CARD_TYPE_VALUABLE
+                ? builder.setValuableCardSecondaryImage(valuableCardSecondaryImage).build() :
+                 builder.build();
     }
 
     @NonNull
@@ -104,6 +165,16 @@
     }
 
     /**
+     * Returns the card type.
+     * @hide
+     */
+    @NonNull
+    @CardType
+    public int getCardType() {
+        return mCardType;
+    }
+
+    /**
      * The visual representation of the card. If the card image Icon is a bitmap, it should have a
      * width of {@link GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
      * GetWalletCardsRequest#getCardHeightPx()}.
@@ -158,23 +229,37 @@
     }
 
     /**
-     * Builder for {@link WalletCard} objects. You must to provide cardId, cardImage,
+    * Visual representation of the card when it is tapped. Includes a barcode to scan the card in
+     * addition to the information in the primary image.
+     * @hide
+    */
+    @Nullable
+    public Icon getValuableCardSecondaryImage() {
+        return mValuableCardSecondaryImage;
+    }
+
+    /**
+     * Builder for {@link WalletCard} objects. You must provide cardId, cardImage,
      * contentDescription, and pendingIntent. If the card is opaque and should be shown with
      * elevation, set hasShadow to true. cardIcon and cardLabel are optional.
      */
     public static final class Builder {
         private String mCardId;
+        private int mCardType;
         private Icon mCardImage;
         private CharSequence mContentDescription;
         private PendingIntent mPendingIntent;
         private Icon mCardIcon;
         private CharSequence mCardLabel;
+        private Icon mValuableCardSecondaryImage;
 
         /**
          * @param cardId             The card id must be non-null and unique within the list of
          *                           cards returned. <b>Note:
          *                           </b> this card ID should <b>not</b> contain PII (Personally
          *                           Identifiable Information, such as username or email address).
+         * @param cardType           Integer representing the card type. The card type must be
+         *                           non-null. If not provided, it defaults to unknown.
          * @param cardImage          The visual representation of the card. If the card image Icon
          *                           is a bitmap, it should have a width of {@link
          *                           GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
@@ -193,15 +278,30 @@
          *                           request device unlock before sending the pending intent. It is
          *                           recommended that the pending intent be immutable (use {@link
          *                           PendingIntent#FLAG_IMMUTABLE}).
+         * @hide
+         */
+        public Builder(@NonNull String cardId,
+                @NonNull @CardType int cardType,
+                @NonNull Icon cardImage,
+                @NonNull CharSequence contentDescription,
+                @NonNull PendingIntent pendingIntent
+        ) {
+            mCardId = cardId;
+            mCardType = cardType;
+            mCardImage = cardImage;
+            mContentDescription = contentDescription;
+            mPendingIntent = pendingIntent;
+        }
+
+        /**
+         * Called when a card type is not provided.
          */
         public Builder(@NonNull String cardId,
                 @NonNull Icon cardImage,
                 @NonNull CharSequence contentDescription,
                 @NonNull PendingIntent pendingIntent) {
-            mCardId = cardId;
-            mCardImage = cardImage;
-            mContentDescription = contentDescription;
-            mPendingIntent = pendingIntent;
+            this(cardId, WalletCard.CARD_TYPE_UNKNOWN, cardImage, contentDescription,
+                    pendingIntent);
         }
 
         /**
@@ -236,6 +336,19 @@
         }
 
         /**
+         * Visual representation of the card when it is tapped. Includes a barcode to scan the card
+         * in addition to the information in the primary image.
+         * @hide
+         */
+        @NonNull
+        public Builder setValuableCardSecondaryImage(@Nullable Icon valuableCardSecondaryImage) {
+            Preconditions.checkState(mCardType == CARD_TYPE_VALUABLE,
+                    "This field can only be set on valuable cards");
+            mValuableCardSecondaryImage = valuableCardSecondaryImage;
+            return this;
+        }
+
+        /**
          * Builds a new {@link WalletCard} instance.
          *
          * @return A built response.
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java
index b7873b7..61f29a4 100644
--- a/core/java/android/util/SparseSetArray.java
+++ b/core/java/android/util/SparseSetArray.java
@@ -139,4 +139,9 @@
     public T valueAt(int intIndex, int valueIndex) {
         return mData.valueAt(intIndex).valueAt(valueIndex);
     }
+
+    /** @return The set of values for key at position {@code intIndex}. */
+    public ArraySet<T> valuesAt(int intIndex) {
+        return mData.valueAt(intIndex);
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 333efad..baee094 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.content.res.Resources.ID_NULL;
+import static android.os.Trace.TRACE_TAG_APP;
 import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
@@ -985,6 +986,22 @@
     private static boolean sAcceptZeroSizeDragShadow;
 
     /**
+     * When true, measure and layout passes of all the newly attached views will be logged with
+     * {@link Trace}, so we can better debug jank due to complex view hierarchies.
+     */
+    private static boolean sTraceLayoutSteps;
+
+    /**
+     * When not null, emits a {@link Trace} instant event and the stacktrace every time a relayout
+     * of a class having this name happens.
+     */
+    private static String sTraceRequestLayoutClass;
+
+    /** Used to avoid computing the full strings each time when layout tracing is enabled. */
+    @Nullable
+    private ViewTraversalTracingStrings mTracingStrings;
+
+    /**
      * Prior to R, {@link #dispatchApplyWindowInsets} had an issue:
      * <p>The modified insets changed by {@link #onApplyWindowInsets} were passed to the
      * entire view hierarchy in prefix order, including siblings as well as siblings of parents
@@ -3532,6 +3549,8 @@
      *                  1               PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE
      *                 1                PFLAG4_DRAG_A11Y_STARTED
      *                1                 PFLAG4_AUTO_HANDWRITING_INITIATION_ENABLED
+     *             1                    PFLAG4_TRAVERSAL_TRACING_ENABLED
+     *            1                     PFLAG4_RELAYOUT_TRACING_ENABLED
      * |-------|-------|-------|-------|
      */
 
@@ -3612,6 +3631,19 @@
      * Indicates that the view enables auto handwriting initiation.
      */
     private static final int PFLAG4_AUTO_HANDWRITING_ENABLED = 0x000010000;
+
+    /**
+     * When set, measure and layout passes of this view will be logged with {@link Trace}, so we
+     * can better debug jank due to complex view hierarchies.
+     */
+    private static final int PFLAG4_TRAVERSAL_TRACING_ENABLED = 0x000040000;
+
+    /**
+     * When set, emits a {@link Trace} instant event and stacktrace every time a requestLayout of
+     * this class happens.
+     */
+    private static final int PFLAG4_RELAYOUT_TRACING_ENABLED = 0x000080000;
+
     /* End of masks for mPrivateFlags4 */
 
     /** @hide */
@@ -6537,6 +6569,15 @@
         out.append(mRight);
         out.append(',');
         out.append(mBottom);
+        appendId(out);
+        if (mAutofillId != null) {
+            out.append(" aid="); out.append(mAutofillId);
+        }
+        out.append("}");
+        return out.toString();
+    }
+
+    void appendId(StringBuilder out) {
         final int id = getId();
         if (id != NO_ID) {
             out.append(" #");
@@ -6568,11 +6609,6 @@
                 }
             }
         }
-        if (mAutofillId != null) {
-            out.append(" aid="); out.append(mAutofillId);
-        }
-        out.append("}");
-        return out.toString();
     }
 
     /**
@@ -8738,7 +8774,8 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+    @TestApi
+    public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
         if (mAttachInfo == null) {
             return;
         }
@@ -8746,6 +8783,7 @@
         getBoundsToScreenInternal(position, clipToParent);
         outRect.set(Math.round(position.left), Math.round(position.top),
                 Math.round(position.right), Math.round(position.bottom));
+        mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
     }
 
     /**
@@ -15550,7 +15588,8 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public void getWindowDisplayFrame(Rect outRect) {
+    @TestApi
+    public void getWindowDisplayFrame(@NonNull Rect outRect) {
         if (mAttachInfo != null) {
             mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
             return;
@@ -20767,6 +20806,14 @@
         if (isFocused()) {
             notifyFocusChangeToImeFocusController(true /* hasFocus */);
         }
+
+        if (sTraceLayoutSteps) {
+            setTraversalTracingEnabled(true);
+        }
+        if (sTraceRequestLayoutClass != null
+                && sTraceRequestLayoutClass.equals(getClass().getSimpleName())) {
+            setRelayoutTracingEnabled(true);
+        }
     }
 
     /**
@@ -23666,6 +23713,30 @@
         return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
     }
 
+    /**
+     * Enable measure/layout debugging on traces.
+     *
+     * @see Trace
+     * @hide
+     */
+    public static void setTraceLayoutSteps(boolean traceLayoutSteps) {
+        sTraceLayoutSteps = traceLayoutSteps;
+    }
+
+    /**
+     * Enable request layout tracing classes with {@code s} simple name.
+     * <p>
+     * When set, a {@link Trace} instant event and a log with the stacktrace is emitted every
+     * time a requestLayout of a class matching {@code s} name happens.
+     * This applies only to views attached from this point onwards.
+     *
+     * @see Trace#instant(long, String)
+     * @hide
+     */
+    public static void setTracedRequestLayoutClassClass(String s) {
+        sTraceRequestLayoutClass = s;
+    }
+
     private boolean setOpticalFrame(int left, int top, int right, int bottom) {
         Insets parentInsets = mParent instanceof View ?
                 ((View) mParent).getOpticalInsets() : Insets.NONE;
@@ -23700,7 +23771,13 @@
     @SuppressWarnings({"unchecked"})
     public void layout(int l, int t, int r, int b) {
         if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
+            if (isTraversalTracingEnabled()) {
+                Trace.beginSection(mTracingStrings.onMeasureBeforeLayout);
+            }
             onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
+            if (isTraversalTracingEnabled()) {
+                Trace.endSection();
+            }
             mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
         }
 
@@ -23713,7 +23790,13 @@
                 setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
 
         if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
+            if (isTraversalTracingEnabled()) {
+                Trace.beginSection(mTracingStrings.onLayout);
+            }
             onLayout(changed, l, t, r, b);
+            if (isTraversalTracingEnabled()) {
+                Trace.endSection();
+            }
 
             if (shouldDrawRoundScrollbar()) {
                 if(mRoundScrollbarRenderer == null) {
@@ -25703,7 +25786,11 @@
         getLocationInWindow(outLocation);
 
         final AttachInfo info = mAttachInfo;
-        if (info != null) {
+
+        // Need to offset the outLocation with the window bounds, but only if "Sandboxing View
+        // Bounds APIs" is disabled. If this override is enabled, it sandboxes {@link outLocation}
+        // within activity bounds.
+        if (info != null && !info.mViewRootImpl.isViewBoundsSandboxingEnabled()) {
             outLocation[0] += info.mWindowLeft;
             outLocation[1] += info.mWindowTop;
         }
@@ -26270,6 +26357,25 @@
         return (viewRoot != null && viewRoot.isInLayout());
     }
 
+    /** To be used only for debugging purposes. */
+    private void printStackStrace(String name) {
+        Log.d(VIEW_LOG_TAG, "---- ST:" + name);
+
+        StringBuilder sb = new StringBuilder();
+        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
+        int startIndex = 1;
+        int endIndex = Math.min(stackTraceElements.length, startIndex + 20); // max 20 entries.
+        for (int i = startIndex; i < endIndex; i++) {
+            StackTraceElement s = stackTraceElements[i];
+            sb.append(s.getMethodName())
+                    .append("(")
+                    .append(s.getFileName())
+                    .append(":")
+                    .append(s.getLineNumber())
+                    .append(") <- ");
+        }
+        Log.d(VIEW_LOG_TAG, name + ": " + sb);
+    }
     /**
      * Call this when something has changed which has invalidated the
      * layout of this view. This will schedule a layout pass of the view
@@ -26283,6 +26389,12 @@
      */
     @CallSuper
     public void requestLayout() {
+        if (isRelayoutTracingEnabled()) {
+            Trace.instantForTrack(TRACE_TAG_APP, "requestLayoutTracing",
+                    mTracingStrings.classSimpleName);
+            printStackStrace(mTracingStrings.requestLayoutStacktracePrefix);
+        }
+
         if (mMeasureCache != null) mMeasureCache.clear();
 
         if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
@@ -26376,8 +26488,14 @@
 
             int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
             if (cacheIndex < 0 || sIgnoreMeasureCache) {
+                if (isTraversalTracingEnabled()) {
+                    Trace.beginSection(mTracingStrings.onMeasure);
+                }
                 // measure ourselves, this should set the measured dimension flag back
                 onMeasure(widthMeasureSpec, heightMeasureSpec);
+                if (isTraversalTracingEnabled()) {
+                    Trace.endSection();
+                }
                 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
             } else {
                 long value = mMeasureCache.valueAt(cacheIndex);
@@ -31547,6 +31665,38 @@
                 == PFLAG4_AUTO_HANDWRITING_ENABLED;
     }
 
+    private void setTraversalTracingEnabled(boolean enabled) {
+        if (enabled) {
+            if (mTracingStrings == null) {
+                mTracingStrings = new ViewTraversalTracingStrings(this);
+            }
+            mPrivateFlags4 |= PFLAG4_TRAVERSAL_TRACING_ENABLED;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_TRAVERSAL_TRACING_ENABLED;
+        }
+    }
+
+    private boolean isTraversalTracingEnabled() {
+        return (mPrivateFlags4 & PFLAG4_TRAVERSAL_TRACING_ENABLED)
+                == PFLAG4_TRAVERSAL_TRACING_ENABLED;
+    }
+
+    private void setRelayoutTracingEnabled(boolean enabled) {
+        if (enabled) {
+            if (mTracingStrings == null) {
+                mTracingStrings = new ViewTraversalTracingStrings(this);
+            }
+            mPrivateFlags4 |= PFLAG4_RELAYOUT_TRACING_ENABLED;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_RELAYOUT_TRACING_ENABLED;
+        }
+    }
+
+    private boolean isRelayoutTracingEnabled() {
+        return (mPrivateFlags4 & PFLAG4_RELAYOUT_TRACING_ENABLED)
+                == PFLAG4_RELAYOUT_TRACING_ENABLED;
+    }
+
     /**
      * Collects a {@link ViewTranslationRequest} which represents the content to be translated in
      * the view.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1834d19..88861ee 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
 import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
 import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
 import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -73,15 +74,16 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -100,6 +102,7 @@
 import android.app.ICompatCameraControlCallback;
 import android.app.ResourcesManager;
 import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -711,6 +714,7 @@
 
     // These are accessed by multiple threads.
     final Rect mWinFrame; // frame given by window manager.
+    private final Rect mLastLayoutFrame;
     Rect mOverrideInsetsFrame;
 
     final Rect mPendingBackDropFrame = new Rect();
@@ -870,6 +874,15 @@
 
     private boolean mRelayoutRequested;
 
+    /**
+     * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
+     * {@link android.view.View#getLocationOnScreen},
+     * {@link android.view.View#getWindowDisplayFrame} and
+     * {@link android.view.View#getWindowVisibleDisplayFrame}
+     * within Activity bounds is enabled for the current application.
+     */
+    private final boolean mViewBoundsSandboxingEnabled;
+
     private int mLastTransformHint = Integer.MIN_VALUE;
 
     /**
@@ -931,6 +944,7 @@
         mHeight = -1;
         mDirty = new Rect();
         mWinFrame = new Rect();
+        mLastLayoutFrame = new Rect();
         mWindow = new W(this);
         mLeashToken = new Binder();
         mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
@@ -956,6 +970,8 @@
         mHandwritingInitiator = new HandwritingInitiator(mViewConfiguration,
                 mContext.getSystemService(InputMethodManager.class));
 
+        mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
         String processorOverrideName = context.getResources().getString(
                                     R.string.config_inputEventCompatProcessorOverrideClassName);
         if (processorOverrideName.isEmpty()) {
@@ -1112,6 +1128,8 @@
         // Update the last resource config in case the resource configuration was changed while
         // activity relaunched.
         updateLastConfigurationFromResources(getConfiguration());
+        // Make sure to report the completion of draw for relaunch with preserved window.
+        reportNextDraw("rebuilt");
     }
 
     private Configuration getConfiguration() {
@@ -1302,7 +1320,7 @@
                         UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
                         mInsetsController.getRequestedVisibilities(), 1f /* compactScale */,
                         mTmpFrames);
-                setFrame(mTmpFrames.frame);
+                setFrame(mTmpFrames.frame, true /* withinRelayout */);
                 registerBackCallbackOnWindow();
                 if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
                     // For apps requesting legacy back behavior, we add a compat callback that
@@ -1823,7 +1841,7 @@
             onMovedToDisplay(displayId, mLastConfigurationFromResources);
         }
 
-        setFrame(frame);
+        setFrame(frame, false /* withinRelayout */);
         mTmpFrames.displayFrame.set(displayFrame);
         if (mTmpFrames.attachedFrame != null && attachedFrame != null) {
             mTmpFrames.attachedFrame.set(attachedFrame);
@@ -2766,7 +2784,7 @@
      * TODO(b/260382739): Apply this to all windows.
      */
     private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
-        return lp.type == TYPE_NOTIFICATION_SHADE;
+        return (lp.privateFlags & PRIVATE_FLAG_OPTIMIZE_MEASURE) != 0;
     }
 
     private Rect getWindowBoundsInsetSystemBars() {
@@ -5739,7 +5757,7 @@
                         mTmpFrames.frame.right = l + w;
                         mTmpFrames.frame.top = t;
                         mTmpFrames.frame.bottom = t + h;
-                        setFrame(mTmpFrames.frame);
+                        setFrame(mTmpFrames.frame, false /* withinRelayout */);
                         maybeHandleWindowMove(mWinFrame);
                     }
                     break;
@@ -8209,7 +8227,7 @@
             // If the position and the size of the frame are both changed, it will trigger a BLAST
             // sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
             // need to send attributes via relayoutAsync.
-            final Rect oldFrame = mWinFrame;
+            final Rect oldFrame = mLastLayoutFrame;
             final Rect newFrame = mTmpFrames.frame;
             final boolean positionChanged =
                     newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
@@ -8339,7 +8357,7 @@
             params.restore();
         }
 
-        setFrame(mTmpFrames.frame);
+        setFrame(mTmpFrames.frame, true /* withinRelayout */);
         return relayoutResult;
     }
 
@@ -8374,8 +8392,18 @@
         mIsSurfaceOpaque = opaque;
     }
 
-    private void setFrame(Rect frame) {
+    /**
+     * Set the mWinFrame of this window.
+     * @param frame the new frame of this window.
+     * @param withinRelayout {@code true} if this setting is within the relayout, or is the initial
+     *                       setting. That will make sure in the relayout process, we always compare
+     *                       the window frame with the last processed window frame.
+     */
+    private void setFrame(Rect frame, boolean withinRelayout) {
         mWinFrame.set(frame);
+        if (withinRelayout) {
+            mLastLayoutFrame.set(frame);
+        }
 
         final WindowConfiguration winConfig = getCompatWindowConfiguration();
         mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop()
@@ -8410,6 +8438,9 @@
      */
     void getDisplayFrame(Rect outFrame) {
         outFrame.set(mTmpFrames.displayFrame);
+        // Apply sandboxing here (in getter) due to possible layout updates on the client after
+        // {@link #mTmpFrames.displayFrame} is received from the server.
+        applyViewBoundsSandboxingIfNeeded(outFrame);
     }
 
     /**
@@ -8426,6 +8457,60 @@
         outFrame.top += insets.top;
         outFrame.right -= insets.right;
         outFrame.bottom -= insets.bottom;
+        // Apply sandboxing here (in getter) due to possible layout updates on the client after
+        // {@link #mTmpFrames.displayFrame} is received from the server.
+        applyViewBoundsSandboxingIfNeeded(outFrame);
+    }
+
+    /**
+     * Offset outRect to make it sandboxed within Window's bounds.
+     *
+     * <p>This is used by {@link android.view.View#getBoundsOnScreen},
+     * {@link android.view.ViewRootImpl#getDisplayFrame} and
+     * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
+     * {@link android.view.View#getWindowDisplayFrame} and
+     * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
+     * {@link android.view.ViewDebug#captureLayers} for debugging.
+     */
+    void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
+        if (isViewBoundsSandboxingEnabled()) {
+            inOutRect.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
+        }
+    }
+
+    /**
+     * Whether the sanboxing of the {@link android.view.View} APIs is enabled.
+     *
+     * <p>This is called by {@link #applyViewBoundsSandboxingIfNeeded} and
+     * {@link android.view.View#getLocationOnScreen} to check if there is a need to add
+     * {@link android.view.View.AttachInfo.mWindowLeft} and
+     * {@link android.view.View.AttachInfo.mWindowTop} offsets.
+     */
+    boolean isViewBoundsSandboxingEnabled() {
+        return mViewBoundsSandboxingEnabled;
+    }
+
+    private boolean getViewBoundsSandboxingEnabled() {
+        if (!CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
+            // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
+            return false;
+        }
+
+        // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
+        try {
+            final List<PackageManager.Property> properties = mContext.getPackageManager()
+                    .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
+
+            final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
+            if (isOptedOut) {
+                // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
+                return false;
+            }
+        } catch (RuntimeException e) {
+            // remote exception.
+        }
+
+        return true;
     }
 
     /**
diff --git a/core/java/android/view/ViewTraversalTracingStrings.java b/core/java/android/view/ViewTraversalTracingStrings.java
new file mode 100644
index 0000000..7dde87b
--- /dev/null
+++ b/core/java/android/view/ViewTraversalTracingStrings.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Trace;
+
+/**
+ * Keeps and caches strings used to trace {@link View} traversals.
+ * <p>
+ * This is done to avoid expensive computations of them every time, which can improve performance.
+ */
+class ViewTraversalTracingStrings {
+
+    /** {@link Trace} tag used to mark {@link View#onMeasure(int, int)}. */
+    public final String onMeasure;
+
+    /** {@link Trace} tag used to mark {@link View#onLayout(boolean, int, int, int, int)}. */
+    public final String onLayout;
+
+    /** Caches the view simple name to avoid re-computations. */
+    public final String classSimpleName;
+
+    /** Prefix for request layout stacktraces output in logs. */
+    public final String requestLayoutStacktracePrefix;
+
+    /** {@link Trace} tag used to mark {@link View#onMeasure(int, int)} happening before layout. */
+    public final String onMeasureBeforeLayout;
+
+    /**
+     * @param v {@link View} from where to get the class name.
+     */
+    ViewTraversalTracingStrings(View v) {
+        String className = v.getClass().getSimpleName();
+        classSimpleName = className;
+        onMeasureBeforeLayout = getTraceName("onMeasureBeforeLayout", className, v);
+        onMeasure = getTraceName("onMeasure", className, v);
+        onLayout = getTraceName("onLayout", className, v);
+        requestLayoutStacktracePrefix = "requestLayout " + className;
+    }
+
+    private String getTraceName(String sectionName, String className, View v) {
+        StringBuilder out = new StringBuilder();
+        out.append(sectionName);
+        out.append(" ");
+        out.append(className);
+        v.appendId(out);
+        return out.substring(0, Math.min(out.length() - 1, Trace.MAX_SECTION_NAME_LEN - 1));
+    }
+}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index abc4926..a01c832 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -814,8 +814,8 @@
     }
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
-     * .Property} for an app to inform the system that the activity can be opted-in or opted-out
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app can be opted-in or opted-out
      * from the compatibility treatment that avoids {@link
      * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
      * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
@@ -833,17 +833,17 @@
      *     <li>Camera compatibility force rotation treatment is active for the package.
      * </ul>
      *
-     * <p>Setting this property to {@code false} informs the system that the activity must be
+     * <p>Setting this property to {@code false} informs the system that the app must be
      * opted-out from the compatibility treatment even if the device manufacturer has opted the app
      * into the treatment.
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
@@ -853,8 +853,81 @@
             "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
-     * .Property} for an app to inform the system that the activity should be excluded from the
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that it needs to be opted-out from the
+     * compatibility treatment that sandboxes {@link android.view.View} API.
+     *
+     * <p>The treatment can be enabled by device manufacturers for applications which misuse
+     * {@link android.view.View} APIs by expecting that
+     * {@link android.view.View#getLocationOnScreen},
+     * {@link android.view.View#getBoundsOnScreen},
+     * {@link android.view.View#getWindowVisibleDisplayFrame},
+     * {@link android.view.View#getWindowDisplayFrame}
+     * return coordinates as if an activity is positioned in the top-left corner of the screen, with
+     * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+     * letterbox modes.
+     *
+     * <p>Setting this property to {@code false} informs the system that the application must be
+     * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
+     * if the device manufacturer has opted the app into the treatment.
+     *
+     * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
+     *     android:value="false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     *
+     * @hide
+     */
+    // TODO(b/263984287): Make this public API.
+    String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
+            "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+
+    /**
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the application can be opted-in or opted-out
+     * from the compatibility treatment that enables sending a fake focus event for unfocused
+     * resumed split screen activities. This is needed because some game engines wait to get
+     * focus before drawing the content of the app which isn't guaranteed by default in multi-window
+     * modes.
+     *
+     * <p>Device manufacturers can enable this treatment using their discretion on a per-device
+     * basis to improve display compatibility. The treatment also needs to be specifically enabled
+     * on a per-app basis afterwards. This can either be done by device manufacturers or developers.
+     *
+     * <p>With this property set to {@code true}, the system will apply the treatment only if the
+     * device manufacturer had previously enabled it on the device. A fake focus event will be sent
+     * to the app after it is resumed only if the app is in split-screen.
+     *
+     * <p>Setting this property to {@code false} informs the system that the activity must be
+     * opted-out from the compatibility treatment even if the device manufacturer has opted the app
+     * into the treatment.
+     *
+     * <p>If the property remains unset the system will apply the treatment only if it had
+     * previously been enabled both at the device and app level by the device manufacturer.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS"
+     *     android:value="true|false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     *
+     * @hide
+     */
+    // TODO(b/263984287): Make this public API.
+    String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
+
+    /**
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app should be excluded from the
      * camera compatibility force rotation treatment.
      *
      * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
@@ -879,11 +952,11 @@
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
@@ -893,8 +966,8 @@
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
-     * .Property} for an app to inform the system that the activity should be excluded
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app should be excluded
      * from the activity "refresh" after the camera compatibility force rotation treatment.
      *
      * <p>The camera compatibility treatment aligns orientations of portrait app window and natural
@@ -926,11 +999,11 @@
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
@@ -940,7 +1013,7 @@
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
 
     /**
-     * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
      * .Property} for an app to inform the system that the activity should be or shouldn't be
      * "refreshed" after the camera compatibility force rotation treatment using "paused ->
      * resumed" cycle rather than "stopped -> resumed".
@@ -976,11 +1049,11 @@
      *
      * <p><b>Syntax:</b>
      * <pre>
-     * &lt;activity&gt;
+     * &lt;application&gt;
      *   &lt;property
      *     android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"
      *     android:value="true|false"/&gt;
-     * &lt;/activity&gt;
+     * &lt;/application&gt;
      * </pre>
      *
      * @hide
@@ -990,6 +1063,77 @@
             "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
 
     /**
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app should be excluded from the
+     * compatibility override for orientation set by the device manufacturer.
+     *
+     * <p>With this property set to {@code true} or unset, device manufacturers can override
+     * orientation for the app using their discretion to improve display compatibility.
+     *
+     * <p>With this property set to {@code false}, device manufactured per-app override for
+     * orientation won't be applied.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"
+     *     android:value="true|false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     *
+     * @hide
+     */
+    // TODO(b/263984287): Make this public API.
+    String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
+            "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
+
+    /**
+     * Application level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for an app to inform the system that the app should be opted-out from the
+     * compatibility override that fixes display orientation to landscape natural orientation when
+     * an activity is fullscreen.
+     *
+     * <p>When this compat override is enabled and while display is fixed to the landscape natural
+     * orientation, the orientation requested by the activity will be still respected by bounds
+     * resolution logic. For instance, if an activity requests portrait orientation, then activity
+     * will appear in the letterbox mode for fixed orientation with the display rotated to the
+     * lanscape natural orientation.
+     *
+     * <p>The treatment is disabled by default but device manufacturers can enable the treatment
+     * using their discretion to improve display compatibility on the displays that have
+     * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
+     * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
+     * for more details).
+     *
+     * <p>With this property set to {@code true} or unset, the system wiil use landscape display
+     * orientation when the following conditions are met:
+     * <ul>
+     *     <li>Natural orientation of the display is landscape
+     *     <li>ignoreOrientationRequest display setting is enabled
+     *     <li>Activity is fullscreen.
+     *     <li>Device manufacturer enabled the treatment.
+     * </ul>
+     *
+     * <p>With this property set to {@code false}, device manufactured per-app override for
+     * display orientation won't be applied.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"
+     *     android:value="true|false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     *
+     * @hide
+     */
+    // TODO(b/263984287): Make this public API.
+    String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
+            "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
+
+    /**
      * @hide
      */
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
@@ -2580,6 +2724,15 @@
         public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
 
         /**
+         * Flag to indicate that the view hierarchy of the window can only be measured when
+         * necessary. If a window size can be known by the LayoutParams, we can use the size to
+         * relayout window, and we don't have to measure the view hierarchy before laying out the
+         * views. This reduces the chances to perform measure.
+         * {@hide}
+         */
+        public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 0x00000200;
+
+        /**
          * Flag that prevents the wallpaper behind the current window from receiving touch events.
          *
          * {@hide}
@@ -2781,6 +2934,7 @@
                 PRIVATE_FLAG_NO_MOVE_ANIMATION,
                 PRIVATE_FLAG_COMPATIBLE_WINDOW,
                 PRIVATE_FLAG_SYSTEM_ERROR,
+                PRIVATE_FLAG_OPTIMIZE_MEASURE,
                 PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                 PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
                 PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
@@ -2841,6 +2995,10 @@
                         equals = PRIVATE_FLAG_SYSTEM_ERROR,
                         name = "SYSTEM_ERROR"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+                        equals = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+                        name = "OPTIMIZE_MEASURE"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 4a4f561..940b133 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -99,7 +99,21 @@
     }
 
     /**
-     * Returns a value between 0 and 1 on how far along the back gesture is.
+     * Returns a value between 0 and 1 on how far along the back gesture is. This value is
+     * driven by the horizontal location of the touch point, and should be used as the fraction to
+     * seek the predictive back animation with. Specifically,
+     * <ol>
+     * <li>The progress is 0 when the touch is at the starting edge of the screen (left or right),
+     * and animation should seek to its start state.
+     * <li>The progress is approximately 1 when the touch is at the opposite side of the screen,
+     * and animation should seek to its end state. Exact end value may vary depending on
+     * screen size.
+     * </ol>
+     * <li> After the gesture finishes in cancel state, this method keeps getting invoked until the
+     * progress value animates back to 0.
+     * </ol>
+     * In-between locations are linearly interpolated based on horizontal distance from the starting
+     * edge and smooth clamped to 1 when the distance exceeds a system-wide threshold.
      */
     public float getProgress() {
         return mProgress;
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 38c52e7..b22f967 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -16,8 +16,10 @@
 
 package android.window;
 
+import android.annotation.NonNull;
 import android.util.FloatProperty;
 
+import com.android.internal.dynamicanimation.animation.DynamicAnimation;
 import com.android.internal.dynamicanimation.animation.SpringAnimation;
 import com.android.internal.dynamicanimation.animation.SpringForce;
 
@@ -123,6 +125,27 @@
         mProgress = 0;
     }
 
+    /**
+     * Animate the back progress animation from current progress to start position.
+     * This should be called when back is cancelled.
+     *
+     * @param finishCallback the callback to be invoked when the progress is reach to 0.
+     */
+    public void onBackCancelled(@NonNull Runnable finishCallback) {
+        final DynamicAnimation.OnAnimationEndListener listener =
+                new DynamicAnimation.OnAnimationEndListener() {
+            @Override
+            public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+                    float velocity) {
+                mSpring.removeEndListener(this);
+                finishCallback.run();
+                reset();
+            }
+        };
+        mSpring.addEndListener(listener);
+        mSpring.animateToFinalPosition(0);
+    }
+
     private void updateProgressValue(float progress) {
         if (mLastBackEvent == null || mCallback == null || !mStarted) {
             return;
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index dd9483a..bfa3447 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -255,11 +255,12 @@
         @Override
         public void onBackCancelled() {
             Handler.getMain().post(() -> {
-                mProgressAnimator.reset();
-                final OnBackAnimationCallback callback = getBackAnimationCallback();
-                if (callback != null) {
-                    callback.onBackCancelled();
-                }
+                mProgressAnimator.onBackCancelled(() -> {
+                    final OnBackAnimationCallback callback = getBackAnimationCallback();
+                    if (callback != null) {
+                        callback.onBackCancelled();
+                    }
+                });
             });
         }
 
diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
index e3cc4f1..d0b5811 100644
--- a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
+++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
@@ -47,7 +47,9 @@
                 /* num_app_provided_app_targets = 6 */ appProvidedApp,
                 /* is_workprofile = 7 */ isWorkprofile,
                 /* previewType = 8 */ typeFromPreviewInt(previewType),
-                /* intentType = 9 */ typeFromIntentString(intent));
+                /* intentType = 9 */ typeFromIntentString(intent),
+                /* num_provided_custom_actions = 10 */ 0,
+                /* reselection_action_provided = 11 */ false);
     }
 
     @Override
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 4f7f8ba..b9373be 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -567,11 +567,6 @@
     public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
 
     /**
-     * (boolean) Whether the clipboard overlay is enabled.
-     */
-    public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
-
-    /**
      * (boolean) Whether widget provider info would be saved to / loaded from system persistence
      * layer as opposed to individual manifests in respective apps.
      */
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 017bf3f..04fc4a6 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -338,4 +338,11 @@
      * @param leftOrTop indicates where the stage split is.
      */
     void enterStageSplitFromRunningApp(boolean leftOrTop);
+
+    /**
+     * Shows the media output switcher dialog.
+     *
+     * @param packageName of the session for which the output switcher is shown.
+     */
+    void showMediaOutputSwitcher(String packageName);
 }
diff --git a/core/java/com/android/internal/util/ObservableServiceConnection.java b/core/java/com/android/internal/util/ObservableServiceConnection.java
index 3165d29..45256fd 100644
--- a/core/java/com/android/internal/util/ObservableServiceConnection.java
+++ b/core/java/com/android/internal/util/ObservableServiceConnection.java
@@ -165,6 +165,13 @@
     }
 
     /**
+     * Executes code on the executor specified at construction.
+     */
+    public void execute(Runnable runnable) {
+        mExecutor.execute(runnable);
+    }
+
+    /**
      * Initiate binding to the service.
      *
      * @return {@code true} if initiating binding succeed, {@code false} if the binding failed or
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 79c5196..3a393b6 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,7 +1,7 @@
 package com.android.internal.util;
 
 import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -11,29 +11,18 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
-import android.graphics.Insets;
-import android.graphics.ParcelableColorSpace;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.WindowManager.ScreenshotSource;
-import android.view.WindowManager.ScreenshotType;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.Objects;
 import java.util.function.Consumer;
 
 public class ScreenshotHelper {
@@ -41,212 +30,6 @@
     public static final int SCREENSHOT_MSG_URI = 1;
     public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
 
-    /**
-     * Describes a screenshot request.
-     */
-    public static class ScreenshotRequest implements Parcelable {
-        @ScreenshotType
-        private final int mType;
-
-        @ScreenshotSource
-        private final int mSource;
-
-        private final Bundle mBitmapBundle;
-        private final Rect mBoundsInScreen;
-        private final Insets mInsets;
-        private final int mTaskId;
-        private final int mUserId;
-        private final ComponentName mTopComponent;
-
-
-        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
-            this(type, source, /* topComponent */ null);
-        }
-
-        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
-                ComponentName topComponent) {
-            this(type,
-                source,
-                /* bitmapBundle*/ null,
-                /* boundsInScreen */ null,
-                /* insets */ null,
-                /* taskId */ -1,
-                /* userId */ -1,
-                topComponent);
-        }
-
-        public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
-                Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
-                ComponentName topComponent) {
-            mType = type;
-            mSource = source;
-            mBitmapBundle = bitmapBundle;
-            mBoundsInScreen = boundsInScreen;
-            mInsets = insets;
-            mTaskId = taskId;
-            mUserId = userId;
-            mTopComponent = topComponent;
-        }
-
-        ScreenshotRequest(Parcel in) {
-            mType = in.readInt();
-            mSource = in.readInt();
-            if (in.readInt() == 1) {
-                mBitmapBundle = in.readBundle(getClass().getClassLoader());
-                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
-                mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
-                mTaskId = in.readInt();
-                mUserId = in.readInt();
-                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
-                        ComponentName.class);
-            } else {
-                mBitmapBundle = null;
-                mBoundsInScreen = null;
-                mInsets = null;
-                mTaskId = -1;
-                mUserId = -1;
-                mTopComponent = null;
-            }
-        }
-
-        @ScreenshotType
-        public int getType() {
-            return mType;
-        }
-
-        @ScreenshotSource
-        public int getSource() {
-            return mSource;
-        }
-
-        public Bundle getBitmapBundle() {
-            return mBitmapBundle;
-        }
-
-        public Rect getBoundsInScreen() {
-            return mBoundsInScreen;
-        }
-
-        public Insets getInsets() {
-            return mInsets;
-        }
-
-        public int getTaskId() {
-            return mTaskId;
-        }
-
-        public int getUserId() {
-            return mUserId;
-        }
-
-        public ComponentName getTopComponent() {
-            return mTopComponent;
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(mType);
-            dest.writeInt(mSource);
-            if (mBitmapBundle == null) {
-                dest.writeInt(0);
-            } else {
-                dest.writeInt(1);
-                dest.writeBundle(mBitmapBundle);
-                dest.writeParcelable(mBoundsInScreen, 0);
-                dest.writeParcelable(mInsets, 0);
-                dest.writeInt(mTaskId);
-                dest.writeInt(mUserId);
-                dest.writeParcelable(mTopComponent, 0);
-            }
-        }
-
-        @NonNull
-        public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
-                new Parcelable.Creator<ScreenshotRequest>() {
-
-                    @Override
-                    public ScreenshotRequest createFromParcel(Parcel source) {
-                        return new ScreenshotRequest(source);
-                    }
-
-                    @Override
-                    public ScreenshotRequest[] newArray(int size) {
-                        return new ScreenshotRequest[size];
-                    }
-                };
-    }
-
-    /**
-     * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
-     * content. This is expected to be used together with {@link #provideScreenshot} to handle a
-     * hardware bitmap as a screenshot.
-     */
-    public static final class HardwareBitmapBundler {
-        private static final String KEY_BUFFER = "bitmap_util_buffer";
-        private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
-
-        private HardwareBitmapBundler() {
-        }
-
-        /**
-         * Creates a Bundle that represents the given Bitmap.
-         * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid
-         * copies when passing across processes, only pass to processes you trust.
-         *
-         * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the
-         * returned Bundle should be treated as a standalone object.
-         *
-         * @param bitmap to convert to bundle
-         * @return a Bundle representing the bitmap, should only be parsed by
-         * {@link #bundleToHardwareBitmap(Bundle)}
-         */
-        public static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
-            if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
-                throw new IllegalArgumentException(
-                        "Passed bitmap must have hardware config, found: " + bitmap.getConfig());
-            }
-
-            // Bitmap assumes SRGB for null color space
-            ParcelableColorSpace colorSpace =
-                    bitmap.getColorSpace() == null
-                            ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
-                            : new ParcelableColorSpace(bitmap.getColorSpace());
-
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
-            bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
-
-            return bundle;
-        }
-
-        /**
-         * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
-         *
-         * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing
-         * this Bitmap on to any other source.
-         *
-         * @param bundle containing the bitmap
-         * @return a hardware Bitmap
-         */
-        public static Bitmap bundleToHardwareBitmap(Bundle bundle) {
-            if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
-                throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
-            }
-
-            HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
-            ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
-                    ParcelableColorSpace.class);
-
-            return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
-                    colorSpace.getColorSpace());
-        }
-    }
-
     private static final String TAG = "ScreenshotHelper";
 
     // Time until we give up on the screenshot & show an error instead.
@@ -277,20 +60,35 @@
     /**
      * Request a screenshot be taken.
      * <p>
-     * Added to support reducing unit test duration; the method variant without a timeout argument
-     * is recommended for general use.
+     * Convenience method for taking a full screenshot with provided source.
      *
-     * @param type The type of screenshot, defined by {@link ScreenshotType}
-     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
-     * @param handler used to process messages received from the screenshot service
+     * @param source             source of the screenshot request, defined by {@link
+     *                           ScreenshotSource}
+     * @param handler            used to process messages received from the screenshot service
      * @param completionConsumer receives the URI of the captured screenshot, once saved or
-     *         null if no screenshot was saved
+     *                           null if no screenshot was saved
      */
-    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
-            @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
-        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
-                completionConsumer);
+    public void takeScreenshot(@ScreenshotSource int source, @NonNull Handler handler,
+            @Nullable Consumer<Uri> completionConsumer) {
+        ScreenshotRequest request =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, source).build();
+        takeScreenshot(request, handler, completionConsumer);
+    }
+
+    /**
+     * Request a screenshot be taken.
+     * <p>
+     *
+     * @param request            description of the screenshot request, either for taking a
+     *                           screenshot or
+     *                           providing a bitmap
+     * @param handler            used to process messages received from the screenshot service
+     * @param completionConsumer receives the URI of the captured screenshot, once saved or
+     *                           null if no screenshot was saved
+     */
+    public void takeScreenshot(ScreenshotRequest request, @NonNull Handler handler,
+            @Nullable Consumer<Uri> completionConsumer) {
+        takeScreenshotInternal(request, handler, completionConsumer, SCREENSHOT_TIMEOUT_MS);
     }
 
     /**
@@ -299,46 +97,16 @@
      * Added to support reducing unit test duration; the method variant without a timeout argument
      * is recommended for general use.
      *
-     * @param type The type of screenshot, defined by {@link ScreenshotType}
-     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
-     * @param handler used to process messages received from the screenshot service
-     * @param timeoutMs time limit for processing, intended only for testing
+     * @param request            description of the screenshot request, either for taking a
+     *                           screenshot or providing a bitmap
+     * @param handler            used to process messages received from the screenshot service
+     * @param timeoutMs          time limit for processing, intended only for testing
      * @param completionConsumer receives the URI of the captured screenshot, once saved or
-     *         null if no screenshot was saved
+     *                           null if no screenshot was saved
      */
     @VisibleForTesting
-    public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
-            @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
-        takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
-    }
-
-    /**
-     * Request that provided image be handled as if it was a screenshot.
-     *
-     * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
-     * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
-     * @param insets The insets that the image was shown with, inside the screen bounds.
-     * @param taskId The taskId of the task that the screen shot was taken of.
-     * @param userId The userId of user running the task provided in taskId.
-     * @param topComponent The component name of the top component running in the task.
-     * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
-     * @param handler A handler used in case the screenshot times out
-     * @param completionConsumer receives the URI of the captured screenshot, once saved or
-     *         null if no screenshot was saved
-     */
-    public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
-            @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
-            @ScreenshotSource int source, @NonNull Handler handler,
-            @Nullable Consumer<Uri> completionConsumer) {
-        ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
-                source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
-        takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
-    }
-
-    private void takeScreenshot(@NonNull Handler handler,
-            ScreenshotRequest screenshotRequest, long timeoutMs,
-            @Nullable Consumer<Uri> completionConsumer) {
+    public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
+            @Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
         synchronized (mScreenshotLock) {
 
             final Runnable mScreenshotTimeout = () -> {
@@ -354,7 +122,7 @@
                 }
             };
 
-            Message msg = Message.obtain(null, 0, screenshotRequest);
+            Message msg = Message.obtain(null, 0, request);
 
             Handler h = new Handler(handler.getLooper()) {
                 @Override
@@ -471,5 +239,4 @@
                 Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
     }
-
 }
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.aidl b/core/java/com/android/internal/util/ScreenshotRequest.aidl
new file mode 100644
index 0000000..b08905d
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.internal.util;
+
+parcelable ScreenshotRequest;
\ No newline at end of file
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java
new file mode 100644
index 0000000..c8b7def
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.java
@@ -0,0 +1,335 @@
+/*
+ * 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.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.ParcelableColorSpace;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.Objects;
+
+/**
+ * Describes a screenshot request.
+ */
+public class ScreenshotRequest implements Parcelable {
+    private static final String TAG = "ScreenshotRequest";
+
+    @WindowManager.ScreenshotType
+    private final int mType;
+    @WindowManager.ScreenshotSource
+    private final int mSource;
+    private final ComponentName mTopComponent;
+    private final int mTaskId;
+    private final int mUserId;
+    private final Bitmap mBitmap;
+    private final Rect mBoundsInScreen;
+    private final Insets mInsets;
+
+    private ScreenshotRequest(
+            @WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source,
+            ComponentName topComponent, int taskId, int userId,
+            Bitmap bitmap, Rect boundsInScreen, Insets insets) {
+        mType = type;
+        mSource = source;
+        mTopComponent = topComponent;
+        mTaskId = taskId;
+        mUserId = userId;
+        mBitmap = bitmap;
+        mBoundsInScreen = boundsInScreen;
+        mInsets = insets;
+    }
+
+    ScreenshotRequest(Parcel in) {
+        mType = in.readInt();
+        mSource = in.readInt();
+        mTopComponent = in.readTypedObject(ComponentName.CREATOR);
+        mTaskId = in.readInt();
+        mUserId = in.readInt();
+        mBitmap = HardwareBitmapBundler.bundleToHardwareBitmap(in.readTypedObject(Bundle.CREATOR));
+        mBoundsInScreen = in.readTypedObject(Rect.CREATOR);
+        mInsets = in.readTypedObject(Insets.CREATOR);
+    }
+
+    @WindowManager.ScreenshotType
+    public int getType() {
+        return mType;
+    }
+
+    @WindowManager.ScreenshotSource
+    public int getSource() {
+        return mSource;
+    }
+
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    public Rect getBoundsInScreen() {
+        return mBoundsInScreen;
+    }
+
+    public Insets getInsets() {
+        return mInsets;
+    }
+
+    public int getTaskId() {
+        return mTaskId;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public ComponentName getTopComponent() {
+        return mTopComponent;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeInt(mSource);
+        dest.writeTypedObject(mTopComponent, 0);
+        dest.writeInt(mTaskId);
+        dest.writeInt(mUserId);
+        dest.writeTypedObject(HardwareBitmapBundler.hardwareBitmapToBundle(mBitmap), 0);
+        dest.writeTypedObject(mBoundsInScreen, 0);
+        dest.writeTypedObject(mInsets, 0);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
+            new Parcelable.Creator<ScreenshotRequest>() {
+
+                @Override
+                public ScreenshotRequest createFromParcel(Parcel source) {
+                    return new ScreenshotRequest(source);
+                }
+
+                @Override
+                public ScreenshotRequest[] newArray(int size) {
+                    return new ScreenshotRequest[size];
+                }
+            };
+
+    /**
+     * Builder class for {@link ScreenshotRequest} objects.
+     */
+    public static class Builder {
+        @WindowManager.ScreenshotType
+        private final int mType;
+
+        @WindowManager.ScreenshotSource
+        private final int mSource;
+
+        private Bitmap mBitmap;
+        private Rect mBoundsInScreen;
+        private Insets mInsets = Insets.NONE;
+        private int mTaskId = INVALID_TASK_ID;
+        private int mUserId = USER_NULL;
+        private ComponentName mTopComponent;
+
+        /**
+         * Begin building a ScreenshotRequest.
+         *
+         * @param type   The type of the screenshot request, defined by {@link
+         *               WindowManager.ScreenshotType}
+         * @param source The source of the screenshot request, defined by {@link
+         *               WindowManager.ScreenshotSource}
+         */
+        public Builder(
+                @WindowManager.ScreenshotType int type,
+                @WindowManager.ScreenshotSource int source) {
+            if (type != TAKE_SCREENSHOT_FULLSCREEN && type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+                throw new IllegalArgumentException("Invalid screenshot type requested!");
+            }
+            mType = type;
+            mSource = source;
+        }
+
+        /**
+         * Construct a new {@link ScreenshotRequest} with the set parameters.
+         */
+        public ScreenshotRequest build() {
+            if (mType == TAKE_SCREENSHOT_FULLSCREEN && mBitmap != null) {
+                Log.w(TAG, "Bitmap provided, but request is fullscreen. Bitmap will be ignored.");
+            }
+            if (mType == TAKE_SCREENSHOT_PROVIDED_IMAGE && mBitmap == null) {
+                throw new IllegalStateException(
+                        "Request is PROVIDED_IMAGE, but no bitmap is provided!");
+            }
+
+            return new ScreenshotRequest(mType, mSource, mTopComponent, mTaskId, mUserId, mBitmap,
+                    mBoundsInScreen, mInsets);
+        }
+
+        /**
+         * Set the top component associated with this request.
+         *
+         * @param topComponent The component name of the top component running in the task.
+         */
+        public Builder setTopComponent(ComponentName topComponent) {
+            mTopComponent = topComponent;
+            return this;
+        }
+
+        /**
+         * Set the task id associated with this request.
+         *
+         * @param taskId The taskId of the task that the screenshot was taken of.
+         */
+        public Builder setTaskId(int taskId) {
+            mTaskId = taskId;
+            return this;
+        }
+
+        /**
+         * Set the user id associated with this request.
+         *
+         * @param userId The userId of user running the task provided in taskId.
+         */
+        public Builder setUserId(int userId) {
+            mUserId = userId;
+            return this;
+        }
+
+        /**
+         * Set the bitmap associated with this request.
+         *
+         * @param bitmap The provided screenshot.
+         */
+        public Builder setBitmap(Bitmap bitmap) {
+            mBitmap = bitmap;
+            return this;
+        }
+
+        /**
+         * Set the bounds for the provided bitmap.
+         *
+         * @param bounds The bounds in screen coordinates that the bitmap originated from.
+         */
+        public Builder setBoundsOnScreen(Rect bounds) {
+            mBoundsInScreen = bounds;
+            return this;
+        }
+
+        /**
+         * Set the insets for the provided bitmap.
+         *
+         * @param insets The insets that the image was shown with, inside the screen bounds.
+         */
+        public Builder setInsets(@NonNull Insets insets) {
+            mInsets = insets;
+            return this;
+        }
+    }
+
+    /**
+     * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
+     * content. This is used together with a fully-defined ScreenshotRequest to handle a hardware
+     * bitmap as a screenshot.
+     */
+    private static final class HardwareBitmapBundler {
+        private static final String KEY_BUFFER = "bitmap_util_buffer";
+        private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
+
+        private HardwareBitmapBundler() {
+        }
+
+        /**
+         * Creates a Bundle that represents the given Bitmap.
+         * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will
+         * avoid
+         * copies when passing across processes, only pass to processes you trust.
+         *
+         * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions,
+         * the
+         * returned Bundle should be treated as a standalone object.
+         *
+         * @param bitmap to convert to bundle
+         * @return a Bundle representing the bitmap, should only be parsed by
+         * {@link #bundleToHardwareBitmap(Bundle)}
+         */
+        private static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
+            if (bitmap == null) {
+                return null;
+            }
+            if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
+                throw new IllegalArgumentException(
+                        "Passed bitmap must have hardware config, found: "
+                                + bitmap.getConfig());
+            }
+
+            // Bitmap assumes SRGB for null color space
+            ParcelableColorSpace colorSpace =
+                    bitmap.getColorSpace() == null
+                            ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
+                            : new ParcelableColorSpace(bitmap.getColorSpace());
+
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
+            bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
+
+            return bundle;
+        }
+
+        /**
+         * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)}.
+         *
+         * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful
+         * passing
+         * this Bitmap on to any other source.
+         *
+         * @param bundle containing the bitmap
+         * @return a hardware Bitmap
+         */
+        private static Bitmap bundleToHardwareBitmap(Bundle bundle) {
+            if (bundle == null) {
+                return null;
+            }
+            if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
+                throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
+            }
+
+            HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
+            ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
+                    ParcelableColorSpace.class);
+
+            return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
+                    colorSpace.getColorSpace());
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index 869da1f..058c6ec 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -106,7 +106,9 @@
      * Enables or disables rotation lock from the system UI toggle.
      */
     public static void setRotationLock(Context context, final boolean enabled) {
-        final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION;
+        final int rotation = areAllRotationsAllowed(context)
+                || useCurrentRotationOnRotationLockChange(context) ? CURRENT_ROTATION
+                : NATURAL_ROTATION;
         setRotationLockAtAngle(context, enabled, rotation);
     }
 
@@ -139,6 +141,11 @@
         return context.getResources().getBoolean(R.bool.config_allowAllRotations);
     }
 
+    private static boolean useCurrentRotationOnRotationLockChange(Context context) {
+        return context.getResources().getBoolean(
+                R.bool.config_useCurrentRotationOnRotationLockChange);
+    }
+
     private static void setRotationLock(final boolean enabled, final int rotation) {
         AsyncTask.execute(new Runnable() {
             @Override
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index 8e4006a..e029af4 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -110,11 +110,20 @@
     // All of this type/caption enabled for current profiles.
     repeated android.content.ComponentNameProto enabled = 3;
 
-
     repeated ManagedServiceInfoProto live_services = 4;
 
+    // Was: repeated ComponentNameProto, when snoozed services were not per-user-id.
+    reserved 5;
+
+    message SnoozedServices {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional int32 user_id = 1;
+        repeated android.content.ComponentNameProto snoozed = 2;
+    }
+
     // Snoozed for current profiles.
-    repeated android.content.ComponentNameProto snoozed = 5;
+    repeated SnoozedServices snoozed = 6;
 }
 
 message RankingHelperProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6a80d1c..31903e2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -315,6 +315,7 @@
     <protected-broadcast android:name="android.media.MASTER_BALANCE_CHANGED_ACTION" />
     <protected-broadcast android:name="android.media.SCO_AUDIO_STATE_CHANGED" />
     <protected-broadcast android:name="android.media.ACTION_SCO_AUDIO_STATE_UPDATED" />
+    <protected-broadcast android:name="com.android.server.audio.action.CHECK_MUSIC_ACTIVE" />
 
     <protected-broadcast android:name="android.intent.action.MEDIA_REMOVED" />
     <protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTED" />
@@ -3893,7 +3894,7 @@
          <p>Should only be requested by the System, should be required by
          TileService declarations.-->
     <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- Allows SystemUI to request third party controls.
          <p>Should only be requested by the System and required by
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index 50e6f33..a5ff470 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -17,6 +17,7 @@
 */
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/item"
               android:orientation="vertical"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 869d5e1..ef746fa 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -330,11 +330,11 @@
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"wys kennisgewings"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Venster-inhoud ophaal"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Die inhoud ondersoek van \'n venster waarmee jy interaksie het."</string>
-    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Verken deur raak aanskakel"</string>
+    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Verken-met-raak aanskakel"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Items waarop getik word, sal hardop gesê word en die skerm kan met behulp van gebare verken word."</string>
-    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Teks wat jy tik waarneem"</string>
+    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Neem teks wat jy tik waar"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Dit sluit persoonlike data soos kredietkaartnommers en wagwoorde in."</string>
-    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Vertoonskermvergroting beheer"</string>
+    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Beheer vertoonskerm-vergroting"</string>
     <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Die vertoonskerm se zoemvlak en posisionering beheer."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Voer gebare uit"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Kan tik, swiep, knyp en ander gebare uitvoer."</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Bekyk tans volskerm"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Swiep van bo na onder as jy wil uitgaan."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Het dit"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Klaar"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Ure se sirkelglyer"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Minute se sirkelglyer"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index d5d8fe5..89039a6 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"ሙሉ ገጽ በማሳየት ላይ"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"ለመውጣት፣ ከላይ ወደታች ጠረግ ያድርጉ።"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"ገባኝ"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"ተከናውኗል"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"የሰዓታት ክብ ተንሸራታች"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"የደቂቃዎች ክብ ተንሸራታች"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index cbf18251..b04af91 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1844,6 +1844,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"جارٍ العرض بملء الشاشة"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"للخروج، مرر بسرعة من أعلى إلى أسفل."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"حسنًا"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"تم"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"شريط التمرير الدائري للساعات"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"شريط التمرير الدائري للدقائق"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 5834cb2..cd69314 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -788,7 +788,7 @@
     <string name="policylab_expirePassword" msgid="6015404400532459169">"স্ক্ৰীন লক পাছৱৰ্ডৰ ম্যাদ ওকলাৰ দিন ছেট কৰক"</string>
     <string name="policydesc_expirePassword" msgid="9136524319325960675">"স্ক্ৰীন লকৰ পাছৱৰ্ড, পিন বা আর্হি কিমান ঘনাই সলনি কৰিব লাগিব তাক সলনি কৰক।"</string>
     <string name="policylab_encryptedStorage" msgid="9012936958126670110">"ষ্ট’ৰেজৰ এনক্ৰিপশ্বন ছেট কৰক"</string>
-    <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"সঞ্চয় কৰি ৰখা ডেটাক এনক্ৰিপ্ট কৰাৰ প্ৰয়োজন।"</string>
+    <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"ষ্ট’ৰ কৰি ৰখা এপৰ ডেটাক এনক্ৰিপ্ট কৰাৰ প্ৰয়োজন।"</string>
     <string name="policylab_disableCamera" msgid="5749486347810162018">"কেমেৰাবোৰ অক্ষম কৰক"</string>
     <string name="policydesc_disableCamera" msgid="3204405908799676104">"আটাইবোৰ ডিভাইচৰ কেমেৰা ব্যৱহাৰ কৰাত বাধা দিয়ক।"</string>
     <string name="policylab_disableKeyguardFeatures" msgid="5071855750149949741">"স্ক্ৰীন লকৰ কিছুমান সুবিধা অক্ষম কৰক"</string>
@@ -1046,9 +1046,9 @@
     <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"আপোনাৰ ৱেব বুকমার্কবোৰ আৰু ইতিহাস পঢ়ক"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰে ব্যৱহাৰ কৰা আটাইবোৰ URLৰ ইতিহাস পঢ়িবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string>
     <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"আপোনাৰ ৱেব বুকমার্কবোৰ আৰু ইতিহাস লিখক"</string>
-    <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"আপোনাৰ টেবলেটত সঞ্চয় কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string>
+    <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"আপোনাৰ টেবলেটত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string>
     <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ ইতিহাস আৰু বুকমার্কবোৰ সংশোধন কৰিবলৈ অনুমতি দিয়ে। ব্ৰাউজাৰ ডেটা মোহাৰিবলৈ অথবা সংশোধন কৰিবলৈ ই এপ্‌টোক অনুমতি দিব পাৰে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ অথবা ৱেব ব্ৰাউজিঙৰ ক্ষমতা থকা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ কৰা নহ’বও পাৰে।"</string>
-    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"আপোনাৰ ফ\'নত সঞ্চয় কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string>
+    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"আপোনাৰ ফ\'নত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string>
     <string name="permlab_setAlarm" msgid="1158001610254173567">"এলাৰ্ম ছেট কৰক"</string>
     <string name="permdesc_setAlarm" msgid="2185033720060109640">"এপ্‌টোক ইনষ্টল হৈ থকা এলাৰ্ম ক্লক এপত এলাৰ্ম ছেট কৰিবলৈ অনুমতি দিয়ে। কিছুমান এলাৰ্ম ক্লক এপত এই সুবিধাটো প্ৰযোজ্য নহ’ব পাৰে।"</string>
     <string name="permlab_addVoicemail" msgid="4770245808840814471">"ভইচমেইল যোগ কৰক"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"স্ক্ৰীন পূৰ্ণৰূপত চাই আছে"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"বাহিৰ হ\'বলৈ ওপৰৰপৰা তললৈ ছোৱাইপ কৰক।"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"বুজি পালোঁ"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"সম্পন্ন কৰা হ’ল"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"ঘড়ীৰ বৃত্তাকাৰ শ্লাইডাৰ"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"মিনিটৰ বৃত্তাকাৰ শ্লাইডাৰ"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 9a4e5ce..5abd2b6 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Tam ekrana baxış"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Çıxmaq üçün yuxarıdan aşağı sürüşdürün."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Anladım"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Hazırdır"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Dairəvi saat slayderi"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Dairəvi dəqiqə slayderi"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index c183da8..4a7a235 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1841,6 +1841,8 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Prikazuje se ceo ekran"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Da biste izašli, prevucite nadole odozgo."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Važi"</string>
+    <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotirajte radi boljeg prikaza"</string>
+    <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Izađite iz podeljenog ekrana radi boljeg prikaza"</string>
     <string name="done_label" msgid="7283767013231718521">"Gotovo"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač za sate"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač za minute"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 2d8f21d..fa0ebc1 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1842,6 +1842,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Прагляд у поўнаэкранным рэжыме"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Для выхаду правядзіце зверху ўніз."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Зразумела"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Гатова"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Кругавы паўзунок гадзін"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Кругавы паўзунок хвілін"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index d0e4b4c..ba4466f 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -328,17 +328,17 @@
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"достъп до сензорните данни за жизнените ви показатели"</string>
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"Известия"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"показване на известията"</string>
-    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Извличане на съдържанието от прозореца"</string>
-    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Инспектиране на съдържанието на прозорец, с който взаимодействате."</string>
-    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Включване на изследването чрез докосване"</string>
+    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Извлича съдържанието от прозореца"</string>
+    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Инспектира съдържанието на прозорец, с който взаимодействате."</string>
+    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Включи изследването чрез докосване"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Докосваните елементи ще бъдат изговаряни на глас и екранът може да бъде изследван посредством жестове."</string>
-    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Наблюдение на въвеждания от вас текст"</string>
+    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Наблюдава въвеждания от вас текст"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Включва лични данни, като например номера на кредитни карти и пароли."</string>
-    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Управление на увеличението на дисплея"</string>
-    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Управление на нивото на мащаба и позиционирането на дисплея."</string>
+    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Управлява увеличението на дисплея"</string>
+    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Управлява нивото на мащаба и позиционирането на дисплея."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Извършване на жестове"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Можете да докосвате, да прекарвате пръст, да събирате пръсти и да извършвате други жестове."</string>
-    <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Жестове за отпечатък"</string>
+    <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Улавя жестове за отпечатък"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Може да улавя жестовете, извършени върху сензора за отпечатъци на устройството."</string>
     <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Създаване на екранна снимка"</string>
     <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да създава екранни снимки."</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Изглед на цял екран"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"За изход плъзнете пръст надолу от горната част."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Разбрах"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Готово"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Кръгов плъзгач за часовете"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Кръгов плъзгач за минутите"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index d11e3eb..ef51384 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -328,13 +328,13 @@
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"আপনার অত্যাবশ্যক লক্ষণগুলির সম্পর্কে সেন্সর ডেটা অ্যাক্সেস করে"</string>
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"বিজ্ঞপ্তি"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"বিজ্ঞপ্তি দেখুন"</string>
-    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"উইন্ডোর কন্টেন্ট পুনরুদ্ধার করে"</string>
-    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ব্যবহার করছেন এমন একটি উইন্ডোর কন্টেন্ট নিরীক্ষণ করে৷"</string>
+    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"উইন্ডোর কন্টেন্ট ফিরিয়ে আনুন"</string>
+    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ব্যবহার করছেন এমন একটি উইন্ডোর কন্টেন্ট পরীক্ষা করে৷"</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"স্পর্শের মাধ্যমে অন্বেষণ করা চালু করুন"</string>
-    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"যে আইটেমগুলিতে আলতো চেপেছেন সেগুলি সশব্দে বলবে এবং ইঙ্গিতগুলি ব্যবহার করে স্ক্রিন অন্বেষণ করা যাবে৷"</string>
+    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"যে আইটেমগুলিতে ট্যাপ করেছেন সেগুলি জোরে বলবে এবং ইঙ্গিতগুলি ব্যবহার করে স্ক্রিন অন্বেষণ করা যাবে৷"</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"আপনার লেখা পাঠ্যকে নিরীক্ষণ করে"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"ক্রেডিট কার্ডের নম্বর ও পাসওয়ার্ডগুলির মতো ব্যক্তিগত তথ্য অন্তর্ভুক্ত করে৷"</string>
-    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"প্রদর্শনের বৃহত্তরীকরণ ব্যবস্থা নিয়ন্ত্রণ করুন"</string>
+    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"ডিসপ্লে বড়কার ব্যবস্থা নিয়ন্ত্রণ করুন"</string>
     <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"প্রদর্শনের জুমের স্তর এবং লোকেশন নির্ধারন নিয়ন্ত্রণ করুন৷"</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"অঙ্গভঙ্গির কাজগুলি সম্পাদন"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"আলতো চাপ দেওয়া, সোয়াইপ, পিঞ্চ করা এবং অন্যান্য ইঙ্গিতের কাজগুলি সম্পাদন করতে পারবেন৷"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"পূর্ণ স্ক্রিনে দেখা হচ্ছে"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"প্রস্থান করতে উপর থেকে নিচের দিকে সোয়াইপ করুন"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"বুঝেছি"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"সম্পন্ন হয়েছে"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"বৃত্তাকার ঘণ্টা নির্বাচকের স্লাইডার"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"বৃত্তাকার মিনিট নির্বাচকের স্লাইডার"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 35f08cc..fd5cd64 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -329,20 +329,20 @@
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"pristupa podacima senzora o vašim vitalnim funkcijama"</string>
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"Obavještenja"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"prikaz obavještenja"</string>
-    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"preuzima sadržaj prozora"</string>
+    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"preuzimati sadržaj prozora"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Pregleda sadržaj prozora koji trenutno koristite."</string>
-    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"uključi opciju Istraživanje dodirom"</string>
-    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Stavke koje dodirnete bit će izgovorene naglas, a ekran možete istraživati koristeći pokrete."</string>
-    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"prati tekst koji unosite"</string>
+    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"uključiti Istraživanje dodirom"</string>
+    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Stavke koje dodirnete će se izgovarati naglas i moći ćete istraživati ekran pomoću pokreta."</string>
+    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"pratiti tekst koji unosite"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Obuhvata lične podatke kao što su brojevi kreditnih kartica i lozinke."</string>
-    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"kontrolira uvećavanje prikaza na ekranu"</string>
-    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrolira stepen uvećanja prikaza na ekranu i podešavanje položaja."</string>
-    <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"izvodi pokrete"</string>
+    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"kontrolirati uvećavanje prikaza na ekranu"</string>
+    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrolira nivo i položaj zumiranja na ekranu."</string>
+    <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"izvoditi pokrete"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Može dodirivati, prevlačiti, hvatati prstima i praviti druge pokrete."</string>
-    <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"prepoznaje pokrete za otisak prsta"</string>
-    <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Moguće je zabilježiti pokrete na senzoru za otisak prsta uređaja."</string>
-    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"pravi snimke ekrana"</string>
-    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Moguće je snimiti ekran."</string>
+    <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"prepoznavati pokrete otiska prsta"</string>
+    <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Može zabilježiti pokrete na senzoru za otisak prsta uređaja."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"praviti snimke ekrana"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Može napraviti snimak ekrana."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"onemogućavanje ili mijenjanje statusne trake"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Dozvoljava aplikaciji onemogućavanje statusne trake ili dodavanje i uklanjanje sistemskih ikona."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"funkcioniranje u vidu statusne trake"</string>
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Prikazuje se cijeli ekran"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Da izađete, prevucite odozgo nadolje."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Razumijem"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Gotovo"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač za odabir sata"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač za minute"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 1934d6e..153f430 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -330,19 +330,19 @@
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"Notificacions"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"mostra notificacions"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Recuperar el contingut de la finestra"</string>
-    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspecciona el contingut d\'una finestra amb què estàs interaccionant."</string>
+    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspeccionar el contingut d\'una finestra amb què estàs interaccionant."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Activar Exploració tàctil"</string>
-    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Els elements que toquis es diran en veu alta, i podràs explorar la pantalla amb gestos."</string>
+    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Els elements que toquis s\'enunciaran en veu alta, i la pantalla es pot explorar amb gestos."</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Observar el text que escrius"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Inclou dades personals com ara números de targetes de crèdit i contrasenyes."</string>
     <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Controlar l\'ampliació de la pantalla"</string>
-    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Controla el nivell i la posició del zoom de la pantalla."</string>
+    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Controlar el nivell i la posició del zoom de la pantalla."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Fer gestos"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Permet tocar, lliscar, pinçar i fer altres gestos."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos d\'empremtes digitals"</string>
-    <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Captura gestos realitzats en el sensor d\'empremtes digitals del dispositiu."</string>
-    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fes una captura de pantalla"</string>
-    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pots fer una captura de la pantalla."</string>
+    <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Pot capturar els gestos fets en el sensor d\'empremtes digitals del dispositiu."</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fer una captura de pantalla"</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pot fer una captura de la pantalla."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desactivar o modificar la barra d\'estat"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Permet que l\'aplicació desactivi la barra d\'estat o afegeixi i elimini icones del sistema."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"aparèixer a la barra d\'estat"</string>
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Mode de pantalla completa"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Per sortir, llisca cap avall des de la part superior."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Entesos"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Fet"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Control circular de les hores"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Control circular dels minuts"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index ea61fea..24617d0 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1842,6 +1842,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Zobrazení celé obrazovky"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Režim ukončíte přejetím prstem shora dolů."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Rozumím"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Hotovo"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Kruhový posuvník hodin"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Kruhový posuvník minut"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index ca619b3..bcc7d50 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visning i fuld skærm"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Stryg ned fra toppen for at afslutte."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK, det er forstået"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Udfør"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Cirkulær timevælger"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Cirkulær minutvælger"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 876d3a1..0df4fc6 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Vollbildmodus wird aktiviert"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Zum Beenden von oben nach unten wischen"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Ok"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Fertig"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Kreisförmiger Schieberegler für Stunden"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Kreisförmiger Schieberegler für Minuten"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 172365f..8c354fb 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Προβολή σε πλήρη οθόνη"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Για έξοδο, σύρετε προς τα κάτω από το επάνω μέρος."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Το κατάλαβα"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Τέλος"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Κυκλικό ρυθμιστικό ωρών"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Κυκλικό ρυθμιστικό λεπτών"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index f42a577..fc37786 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Done"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 76ca7cf..7d455c7 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1840,6 +1840,8 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string>
+    <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string>
+    <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Exit split screen for a better view"</string>
     <string name="done_label" msgid="7283767013231718521">"Done"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index e627764..7bc59d7 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Done"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 565cbd9..997929d 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Done"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 2718d42..b8767b7b 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1840,6 +1840,8 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎Viewing full screen‎‏‎‎‏‎"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‎‎‎To exit, swipe down from the top.‎‏‎‎‏‎"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‎‏‏‎Got it‎‏‎‎‏‎"</string>
+    <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‎Rotate for a better view‎‏‎‎‏‎"</string>
+    <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‎‎Exit split screen for a better view‎‏‎‎‏‎"</string>
     <string name="done_label" msgid="7283767013231718521">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎Done‎‏‎‎‏‎"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎Hours circular slider‎‏‎‎‏‎"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎Minutes circular slider‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 4e5c8f7..884b767 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visualización en pantalla completa"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Para salir, desliza el dedo hacia abajo desde la parte superior."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendido"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Listo"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Control deslizante circular de horas"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Control deslizante circular de minutos"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 54e22d3..b26ee6a 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Modo de pantalla completa"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Para salir, desliza el dedo de arriba abajo."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendido"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Hecho"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Control deslizante circular de horas"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Control deslizante circular de minutos"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index c727137..0c95591 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Kuvamine täisekraanil"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Väljumiseks pühkige ülevalt alla."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Selge"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Valmis"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Ringikujuline tunniliugur"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Ringikujuline minutiliugur"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index e60a96e..0dffb90 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -78,7 +78,7 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenik gabe."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Zerbitzua ez da hornitu."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ezin duzu aldatu deitzailearen identitatearen ezarpena."</string>
-    <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ez dago mugikorreko datu-zerbitzurik"</string>
+    <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ez dago mugikorretarako datu-zerbitzurik"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Ezin da egin larrialdi-deirik"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ez dago ahots-deien zerbitzurik"</string>
     <string name="RestrictedOnAllVoiceTitle" msgid="3982069078579103087">"Ez dago ahozko zerbitzurik eta ezin da egin larrialdi-deirik"</string>
@@ -328,19 +328,19 @@
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"atzitu bizi-konstanteei buruzko sentsorearen datuak"</string>
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"Jakinarazpenak"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"jakinarazpenak erakutsi"</string>
-    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Eskuratu leihoko edukia"</string>
+    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Leihoko edukia eskuratu."</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Arakatu irekita daukazun leihoko edukia."</string>
-    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Aktibatu \"Arakatu ukituta\""</string>
+    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"\"Arakatu ukituta\" aktibatu."</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Sakatutako elementuak ozen irakurriko dira eta pantaila keinu bidez arakatu ahal izango da."</string>
-    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Behatu idazten duzun testua"</string>
+    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Idazten duzun testua behatu."</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Ez da salbuespenik egiten datu pertsonalekin, hala nola kreditu-txartelen zenbakiekin eta pasahitzekin."</string>
-    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Kontrolatu pantailaren zoom-maila"</string>
-    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrolatu pantailaren zoom-maila eta posizioa."</string>
-    <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Egin keinuak"</string>
+    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Pantailaren zoom-maila kontrolatu."</string>
+    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Pantailaren zoom-maila eta posizioa kontrolatu."</string>
+    <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Keinuak egin."</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Sakatu, lerratu, atximurkatu eta beste hainbat keinu egin ditzake."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Hatz-marken keinuak"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Gailuaren hatz-marken sentsorean egindako keinuak atzeman ditzake."</string>
-    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Atera pantaila-argazki bat"</string>
+    <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Pantaila-argazkiak atera."</string>
     <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pantaila-argazkiak atera ditzake."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desgaitu edo aldatu egoera-barra"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Egoera-barra desgaitzea edo sistema-ikonoak gehitzea edo kentzea baimentzen die aplikazioei."</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Pantaila osoko ikuspegia"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Irteteko, pasatu hatza goitik behera."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Ados"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Eginda"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Ordua aukeratzeko ikuspegi zirkularra"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Minutuak aukeratzeko ikuspegi zirkularra"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index f9fa48d..20b6745 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -575,7 +575,7 @@
     <string name="permdesc_mediaLocation" msgid="597912899423578138">"به برنامه اجازه می‌دهد مکان‌ها را از مجموعه رسانه‌تان بخواند."</string>
     <string name="biometric_app_setting_name" msgid="3339209978734534457">"استفاده از زیست‌سنجشی"</string>
     <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"استفاده از زیست‌سنجشی یا قفل صفحه"</string>
-    <string name="biometric_dialog_default_title" msgid="55026799173208210">"تأیید کنید این شما هستید"</string>
+    <string name="biometric_dialog_default_title" msgid="55026799173208210">"تأیید کنید این شمایید"</string>
     <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"برای ادامه، از زیست‌سنجشی استفاده کنید"</string>
     <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"برای ادامه، از زیست‌سنجشی یا قفل صفحه استفاده کنید"</string>
     <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"سخت‌افزار زیست‌سنجی دردسترس نیست"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"مشاهده در حالت تمام صفحه"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"برای خروج، انگشتتان را از بالای صفحه به پایین بکشید."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"متوجه شدم"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"تمام"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"لغزنده دایره‌ای ساعت"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"لغزنده دایره‌ای دقیقه"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index c81bf00..5f27e36 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1840,6 +1840,8 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Koko ruudun tilassa"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Sulje palkki pyyhkäisemällä alas ruudun ylälaidasta."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Selvä"</string>
+    <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Kierrä, niin saat paremman näkymän"</string>
+    <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Poistu jaetulta näytöltä, niin saat paremman näkymän"</string>
     <string name="done_label" msgid="7283767013231718521">"Valmis"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Tuntien ympyränmuotoinen liukusäädin"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Minuuttien ympyränmuotoinen liukusäädin"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 40b265a..eb8a778 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Affichage plein écran"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Pour quitter, balayez vers le bas à partir du haut."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Terminé"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Curseur circulaire des heures"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Curseur circulaire des minutes"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index c2a8460..480a7b5 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Affichage en plein écran"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Pour quitter, balayez l\'écran du haut vers le bas."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"OK"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Curseur circulaire des heures"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Curseur circulaire des minutes"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 9353cf8..1e8063a 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Vendo pantalla completa"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Para saír, pasa o dedo cara abaixo desde a parte superior."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendido"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Feito"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Control desprazable circular das horas"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Control desprazable circular dos minutos"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index ad006c6..45f12d7 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"પૂર્ણ સ્ક્રીન પર જુઓ"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"બહાર નીકળવા માટે, ટોચ પરથી નીચે સ્વાઇપ કરો."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"સમજાઈ ગયું"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"થઈ ગયું"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"કલાકનું વર્તુળાકાર સ્લાઇડર"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"મિનિટનું વર્તુળાકાર સ્લાઇડર"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 2a472f8..f145a4c 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"आप पूरे स्क्रीन पर देख रहे हैं"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"बाहर निकलने के लिए, ऊपर से नीचे स्वा‍इप करें."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"ठीक है"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"हो गया"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"घंटो का चक्राकार स्लाइडर"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"मिनटों का चक्राकार स्लाइडर"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 72480a4..e126941 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Gledanje preko cijelog zaslona"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Za izlaz prijeđite prstom od vrha prema dolje."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Shvaćam"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Gotovo"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač sati"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač minuta"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index f9a93a8..a529352 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Megtekintése teljes képernyőn"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Kilépéshez csúsztassa ujját fentről lefelé."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Értem"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Kész"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Óra kör alakú csúszkája"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Perc kör alakú csúszkája"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 138d810..825924c 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Լիաէկրան դիտում"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Դուրս գալու համար վերևից սահահարվածեք դեպի ներքև:"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Պարզ է"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Պատրաստ է"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Ժամերի ընտրություն թվատախտակից"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Րոպեների ընտրություն թվատախտակից"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index b02345a..8768e4d 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Melihat layar penuh"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Untuk keluar, geser layar ke bawah dari atas."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Mengerti"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Selesai"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Penggeser putar jam"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Penggeser putar menit"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 77df331..9a27d8d 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Notar allan skjáinn"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Strjúktu niður frá efri brún til að hætta."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Ég skil"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Lokið"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Valskífa fyrir klukkustundir"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Valskífa fyrir mínútur"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 13f265e..8a6ef85 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visualizzazione a schermo intero"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Per uscire, scorri dall\'alto verso il basso."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Fine"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Dispositivo di scorrimento circolare per le ore"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Dispositivo di scorrimento circolare per i minuti"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 30426c9..4ad7c2c 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"צפייה במסך מלא"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"כדי לצאת, פשוט מחליקים אצבע מלמעלה למטה."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"הבנתי"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"סיום"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"מחוון שעות מעגלי"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"מחוון דקות מעגלי"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 2700f02..917f911 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"全画面表示"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"終了するには、上から下にスワイプします。"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"完了"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"円形スライダー(時)"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"円形スライダー(分)"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 7608199..e8b84e2 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"სრულ ეკრანზე ნახვა"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"გამოსვლისათვის, გაასრიალეთ ზემოდან ქვემოთ."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"გასაგებია"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"დასრულდა"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"საათების წრიული სლაიდერი"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"წუთების წრიული სლაიდერი"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 8b27a9e..306af3a 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -333,7 +333,7 @@
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Түртілген элементтерді дыбыстау функциясын қосу"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Түртілген элементтер дауыстап айтылады және экранды қимылдар арқылы зерттеуге болады."</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Терілген мәтінді тексеру"</string>
-    <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Несиелік карта нөмірі және құпия сөздер сияқты жеке деректі қоса."</string>
+    <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Несиелік карта нөмірлері және құпия сөздер сияқты жеке деректерді қамтиды."</string>
     <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Дисплей ұлғайтуды басқару"</string>
     <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Дисплейдің масштабтау деңгейін және орналастыруды басқару."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Қимылдарды орындау"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Толық экранда көру"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Шығу үшін жоғарыдан төмен қарай сырғытыңыз."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Түсінікті"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Дайын"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Сағаттар айналымының қозғалтқышы"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Минут айналымын қозғалтқыш"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index b115d8f..2f4b516 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -329,7 +329,7 @@
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"ការ​ជូនដំណឹង"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"បង្ហាញ​ការជូនដំណឹង"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ទាញយក​ខ្លឹមសារ​វិនដូ"</string>
-    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ពិនិត្យ​ខ្លឹមសារវិនដូ​ដែល​អ្នក​កំពុង​ទាក់ទង​ជា​មួយ។"</string>
+    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ពិនិត្យ​ខ្លឹមសារវិនដូ​ដែល​អ្នក​កំពុង​ធ្វើអន្តរកម្ម​ជា​មួយ។"</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"បើក​ការ​រក​មើល​​ដោយ​ប៉ះ"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"ធាតុដែលបានប៉ះនឹងត្រូវបានអានឮៗ ហើយអេក្រង់នោះអាចត្រូវបានស្វែងរកដោយប្រើកាយវិការ។"</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"មើល​អត្ថបទ​ដែល​វាយ"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"កំពុងមើលពេញអេក្រង់"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"ដើម្បីចាកចេញ សូមអូសពីលើចុះក្រោម។"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"យល់ហើយ"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"រួចរាល់"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"គ្រាប់​រំកិល​រង្វង់​ម៉ោង"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"គ្រាប់​រំកិល​រង្វង់​នាទី"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index d923937..a04faa5 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"ಪೂರ್ಣ ಪರದೆಯನ್ನು ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"ನಿರ್ಗಮಿಸಲು, ಮೇಲಿನಿಂದ ಕೆಳಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"ತಿಳಿಯಿತು"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"ಮುಗಿದಿದೆ"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"ಗಂಟೆಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"ನಿಮಿಷಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 4df0322..4974f95 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"전체 화면 모드"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"종료하려면 위에서 아래로 스와이프합니다."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"확인"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"완료"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"시간 원형 슬라이더"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"분 원형 슬라이더"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 3db4e9b..cbc29dc 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -35,7 +35,7 @@
     <string name="mmiError" msgid="2862759606579822246">"Туташууда көйгөй чыкты же MMI коду жараксыз."</string>
     <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Функция колдоого алынбайт."</string>
     <string name="mmiFdnError" msgid="3975490266767565852">"Иш-аракет туруктуу терүү номерлери менен гана чектелет."</string>
-    <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Роуминг учурунда чалууну башка номерге багыттоонун жөндөөлөрүн телефонуңуздан өзгөртүү мүмкүн эмес."</string>
+    <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Роуминг учурунда чалууну башка номерге багыттоонун параметрлерин телефонуңуздан өзгөртүү мүмкүн эмес."</string>
     <string name="serviceEnabled" msgid="7549025003394765639">"Кызмат иштетилди."</string>
     <string name="serviceEnabledFor" msgid="1463104778656711613">"Кызмат төмөнкү үчүн иштетилди:"</string>
     <string name="serviceDisabled" msgid="641878791205871379">"Кызмат өчүрүлдү."</string>
@@ -77,7 +77,7 @@
     <string name="CLIRDefaultOffNextCallOn" msgid="1022781126694885017">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелген"</string>
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелбейт"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Кызмат камсыздалган эмес."</string>
-    <string name="CLIRPermanent" msgid="166443681876381118">"Чалуучунун далдаштырма дайындары жөндөөлөрүн өзгөртө албайсыз."</string>
+    <string name="CLIRPermanent" msgid="166443681876381118">"Чалуучунун далдаштырма дайындары параметрлерин өзгөртө албайсыз."</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобилдик Интернет кызматы жок"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Шашылыш чалуу бөгөттөлгөн"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Аудио чалуу кызматы бөгөттөлгөн"</string>
@@ -264,7 +264,7 @@
     <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Учак режими"</string>
     <string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"Учак режими КҮЙҮК"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"Учак режими ӨЧҮК"</string>
-    <string name="global_action_settings" msgid="4671878836947494217">"Жөндөөлөр"</string>
+    <string name="global_action_settings" msgid="4671878836947494217">"Параметрлер"</string>
     <string name="global_action_assist" msgid="2517047220311505805">"Жардам"</string>
     <string name="global_action_voice_assist" msgid="6655788068555086695">"Үн жардамчысы"</string>
     <string name="global_action_lockdown" msgid="2475471405907902963">"Бекем кулпулоо"</string>
@@ -328,7 +328,7 @@
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"организмдин абалына көз салган сенсордун дайындарына мүмкүнчүлүк алуу"</string>
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"Билдирмелер"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"билдирмелерди көрсөтүү"</string>
-    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Терезедеги мазмунду алып турат"</string>
+    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Терезедеги нерселерди алып туруу"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Учурда ачылып турган терезедеги маалыматты талдайт."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"\"Сыйпалап изилдөө\" мүмкүнчүлүгүн иштетет"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Басылып жаткан элементтерди айтып турат жана түзмөктү жаңсоолор менен башкаруу мүмкүнчүлүгүн иштетет."</string>
@@ -341,7 +341,7 @@
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Манжа изинин жаңсоолору"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Түзмөктөгү манжа изинин сенсорунда жасалган жаңсоолорду жаздырып алат."</string>
     <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Скриншот тартып алуу"</string>
-    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Дисплейдин скриншотун тартып алууга болот."</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Дисплейдин скриншотун тартып алсаңыз болот."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"абал тилкесин өчүрүү же өзгөртүү"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Колдонмого абал тилкесин өчүрүү же тутум сүрөтчөлөрүн кошуу же алып салуу мүмкүнчүлүгүн берет."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"абал тилкесинин милдетин аткаруу"</string>
@@ -403,7 +403,7 @@
     <string name="permlab_getPackageSize" msgid="375391550792886641">"колдонмо сактагычынын мейкиндигин өлчөө"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Колдонмого өз кодун, дайындарын жана кэш өлчөмдөрүн түшүрүп алуу мүмкүнчүлүгүн берет"</string>
     <string name="permlab_writeSettings" msgid="8057285063719277394">"система тууралоолорун өзгөртүү"</string>
-    <string name="permdesc_writeSettings" msgid="8293047411196067188">"Колдонмого системанын коопсуздук жөндөөлөрүнүн дайындарын өзгөртүү мүмкүнчүлүгүн берет. Кесепттүү колдонмолор тутумуңуздун конфигурациясын бузуп салышы мүмкүн."</string>
+    <string name="permdesc_writeSettings" msgid="8293047411196067188">"Колдонмого системанын коопсуздук параметрлеринин дайындарын өзгөртүү мүмкүнчүлүгүн берет. Кесепттүү колдонмолор тутумуңуздун конфигурациясын бузуп салышы мүмкүн."</string>
     <string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"түзмөктү жандырганда иштеп баштоо"</string>
     <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"Колдонмого тутум жүктөлүп бүтөөрү менен өзүн-өзү иштетүү мүмкүнчүлүгүн берет. Бул планшеттин ишке киргизилишин кыйла создуктуруп, планшеттин үзгүлтүксүз иштешин жайлатып салышы мүмкүн."</string>
     <string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"Тутум күйгүзүлөрү менен колдонмого өз алдынча иштеп баштоого уруксат берет. Ага байланыштуу Android TV түзмөгүңүз кечирээк күйгүзүлүп, ошондой эле колдонмо такай иштеп тургандыктан, түзмөк жайыраак иштеп калышы мүмкүн."</string>
@@ -446,7 +446,7 @@
     <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"Колдонмо кайда жүргөнүңүздү активдүү режимде гана болжолдуу аныктай алат. Ал үчүн түзмөгүңүздө жайгашкан жерди аныктоо кызматын иштетишиңиз керек."</string>
     <string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"жайгашкан жерди фондо аныктоо"</string>
     <string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Колдонмо кайда жүргөнүңүздү активдүү режимде гана эмес, фондук режимде да аныктай алат."</string>
-    <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио жөндөөлөрүңүздү өзгөртүңүз"</string>
+    <string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио параметрлериңизди өзгөртүңүз"</string>
     <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Колдонмого үн деңгээли жана кайсы динамик аркылуу үн чыгарылышы керек сыяктуу түзмөктүн аудио тууралоолорун өзгөртүүгө уруксат берет."</string>
     <string name="permlab_recordAudio" msgid="1208457423054219147">"аудио жаздыруу"</string>
     <string name="permdesc_recordAudio" msgid="5857246765327514062">"Бул колдонмо иштеп жатканда микрофон менен аудио файлдарды жаздыра алат."</string>
@@ -631,7 +631,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Жүзүңүздүн үлгүсүн өчүрүү үчүн басып, жаңы үлгүнү кошуңуз"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Жүзүнөн таанып ачууну тууралоо"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Телефонуңузду карап туруп эле кулпусун ачып алыңыз"</string>
-    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Жүзүнөн таанып ачуу функциясын колдонуу үчүн Жөндөөлөр &gt; Купуялык бөлүмүнө өтүп, "<b>"Камераны колдонууну"</b>" күйгүзүңүз"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Жүзүнөн таанып ачуу функциясын колдонуу үчүн Параметрлер &gt; Купуялык бөлүмүнө өтүп, "<b>"Камераны колдонууну"</b>" күйгүзүңүз"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Кулпусун ачуунун көбүрөөк жолдорун жөндөңүз"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Манжа изин кошуу үчүн басыңыз"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Кулпуланган түзмөктү манжа изи менен ачуу"</string>
@@ -687,7 +687,7 @@
   </string-array>
     <string name="face_error_vendor_unknown" msgid="7387005932083302070">"Бир жерден ката кетти. Кайра аракет кылыңыз."</string>
     <string name="face_icon_content_description" msgid="465030547475916280">"Жүздүн сүрөтчөсү"</string>
-    <string name="permlab_readSyncSettings" msgid="6250532864893156277">"шайкештирүү жөндөөлөрүн окуу"</string>
+    <string name="permlab_readSyncSettings" msgid="6250532864893156277">"шайкештирүү параметрлерин окуу"</string>
     <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Колдонмого эсеп менен синхрондошуу тууралоолорун окуганга уруксат берет. Мисалы, Кишилер колдонмосу эсеп менен синхрондошкондугун аныктай алат."</string>
     <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"синхрондоштурууну өчүрүү/жандыруу"</string>
     <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Колдонмого эсеп менен синхрондошуу тууралоолорун өзгөртүү уруксатын берет. Мисалы, бул Кишилер колдонмосун эсеп менен синхрондошуусун иштете алат."</string>
@@ -1229,7 +1229,7 @@
     <string name="launch_warning_original" msgid="3332206576800169626">"Башында <xliff:g id="APP_NAME">%1$s</xliff:g> жүргүзүлгөн."</string>
     <string name="screen_compat_mode_scale" msgid="8627359598437527726">"Шкала"</string>
     <string name="screen_compat_mode_show" msgid="5080361367584709857">"Ар дайым көрүнсүн"</string>
-    <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Муну тутум жөндөөлөрүнөн кайра иштетүү &gt; Колдонмолор &gt; Жүктөлүп алынган."</string>
+    <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Муну тутум параметрлеринен кайра иштетүү &gt; Колдонмолор &gt; Жүктөлүп алынган."</string>
     <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу көрүнүштүн тандалган өлчөмүн экранда көрсөтө албайт жана туура эмес иштеши мүмкүн."</string>
     <string name="unsupported_display_size_show" msgid="980129850974919375">"Ар дайым көрүнсүн"</string>
     <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> Android OS тутуму менен иштеген түзмөктүн шайкеш келбеген версиясы үчүн орнотулган колдонмо жана туура эмес иштеши мүмкүн. Колдонмонун жаңырган версиясы жеткиликтүү болушу мүмкүн."</string>
@@ -1498,7 +1498,7 @@
     <string name="vpn_lockdown_connected" msgid="2853127976590658469">"Туташты"</string>
     <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Ар дайым иштеген VPN\'ден ажыратуу"</string>
     <string name="vpn_lockdown_error" msgid="4453048646854247947">"Ар дайым күйүк VPN\'ге туташпай калды"</string>
-    <string name="vpn_lockdown_config" msgid="8331697329868252169">"Тармакты же VPN жөндөөлөрүн өзгөртүү"</string>
+    <string name="vpn_lockdown_config" msgid="8331697329868252169">"Тармакты же VPN параметрлерин өзгөртүү"</string>
     <string name="upload_file" msgid="8651942222301634271">"Файл тандоо"</string>
     <string name="no_file_chosen" msgid="4146295695162318057">"Эч файл тандалган жок"</string>
     <string name="reset" msgid="3865826612628171429">"Баштапкы абалга келтирүү"</string>
@@ -1624,7 +1624,7 @@
     <string name="media_route_chooser_title" msgid="6646594924991269208">"Түзмөккө туташуу"</string>
     <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"Тышкы экранга чыгаруу"</string>
     <string name="media_route_chooser_searching" msgid="6119673534251329535">"Түзмөктөр изделүүдө..."</string>
-    <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Жөндөөлөр"</string>
+    <string name="media_route_chooser_extended_settings" msgid="2506352159381327741">"Параметрлер"</string>
     <string name="media_route_controller_disconnect" msgid="7362617572732576959">"Ажыратуу"</string>
     <string name="media_route_status_scanning" msgid="8045156315309594482">"Скандоодо..."</string>
     <string name="media_route_status_connecting" msgid="5845597961412010540">"Туташууда..."</string>
@@ -1679,10 +1679,10 @@
     <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ыкчам иштетесизби?"</string>
     <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Атайын мүмкүнчүлүктөр функциясын пайдалануу үчүн ал күйгүзүлгөндө, үндү катуулатып/акырындаткан эки баскычты тең 3 секунддай коё бербей басып туруңуз."</string>
     <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Атайын мүмкүнчүлүктөрдүн ыкчам баскычын иштетесизби?"</string>
-    <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Атайын мүмкүнчүлүктөр функциясын иштетүү үчүн үндү катуулатуу/акырындатуу баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nУчурдагы функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТандалган функцияларды өзгөртүү үчүн Жөндөөлөр &gt; Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
+    <string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Атайын мүмкүнчүлүктөр функциясын иштетүү үчүн үндү катуулатуу/акырындатуу баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nУчурдагы функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТандалган функцияларды өзгөртүү үчүн Параметрлер &gt; Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
     <string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
     <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ыкчам баскычын иштетесизби?"</string>
-    <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматын иштетүү үчүн үндү катуулатуу/акырындатуу баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nБаскычтардын ушул айкалышын башка функцияга дайындоо үчүн, Жөндөөлөр &gt; Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
+    <string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматын иштетүү үчүн үндү катуулатуу/акырындатуу баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nБаскычтардын ушул айкалышын башка функцияга дайындоо үчүн, Параметрлер &gt; Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
     <string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ооба"</string>
     <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Жок"</string>
     <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"КҮЙҮК"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Толук экран режими"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Чыгуу үчүн экранды ылдый сүрүп коюңуз."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Түшүндүм"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Даяр"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Саат жебеси"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Мүнөт жебеси"</string>
@@ -2064,12 +2068,12 @@
     <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Көбүрөөк маалымат алып, өзгөртүү үчүн таптаңыз."</string>
     <string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\"Тынчымды алба\" режими өзгөрдү"</string>
     <string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Бөгөттөлгөн нерселерди көрүү үчүн таптаңыз."</string>
-    <string name="review_notification_settings_title" msgid="5102557424459810820">"Билдирмелердин жөндөөлөрүн карап чыгуу"</string>
+    <string name="review_notification_settings_title" msgid="5102557424459810820">"Билдирмелердин параметрлерин карап чыгуу"</string>
     <string name="review_notification_settings_text" msgid="5916244866751849279">"Android 13 версиясынан баштап билдирмелерди жөнөтүү үчүн орноткон колдонмолоруңузга уруксат берүү керек. Учурдагы колдонмолор үчүн бул уруксатты өзгөртүү үчүн таптап коюңуз."</string>
     <string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Кийинчерээк эскертүү"</string>
     <string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Жабуу"</string>
     <string name="notification_app_name_system" msgid="3045196791746735601">"Тутум"</string>
-    <string name="notification_app_name_settings" msgid="9088548800899952531">"Жөндөөлөр"</string>
+    <string name="notification_app_name_settings" msgid="9088548800899952531">"Параметрлер"</string>
     <string name="notification_appops_camera_active" msgid="8177643089272352083">"Камера"</string>
     <string name="notification_appops_microphone_active" msgid="581333393214739332">"Микрофон"</string>
     <string name="notification_appops_overlay_active" msgid="5571732753262836481">"экрандагы башка терезелердин үстүнөн көрсөтүлүүдө"</string>
@@ -2270,7 +2274,7 @@
     <string name="config_pdp_reject_user_authentication_failed" msgid="4531693033885744689"></string>
     <string name="config_pdp_reject_service_not_subscribed" msgid="8190338397128671588"></string>
     <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="6024904218067254186"></string>
-    <string name="window_magnification_prompt_title" msgid="2876703640772778215">"Чоңойтуу функциясынын жаңы жөндөөлөрү"</string>
+    <string name="window_magnification_prompt_title" msgid="2876703640772778215">"Чоңойтуу функциясынын жаңы параметрлери"</string>
     <string name="window_magnification_prompt_content" msgid="8159173903032344891">"Эми экрандын бир бөлүгүн чоңойто аласыз"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Жөндөөлөрдөн күйгүзүү"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Жабуу"</string>
@@ -2281,7 +2285,7 @@
     <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Сенсордун купуялыгы"</string>
     <string name="splash_screen_view_icon_description" msgid="180638751260598187">"Колдонмонун сүрөтчөсү"</string>
     <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Колдонмонун брендинин сүрөтү"</string>
-    <string name="view_and_control_notification_title" msgid="4300765399209912240">"Кирүү мүмкүнчүлүгүнүн жөндөөлөрүн текшериңиз"</string>
+    <string name="view_and_control_notification_title" msgid="4300765399209912240">"Кирүү мүмкүнчүлүгүнүн параметрлерин текшериңиз"</string>
     <string name="view_and_control_notification_content" msgid="8003766498562604034">"<xliff:g id="SERVICE_NAME">%s</xliff:g> экраныңызды көрүп, көзөмөлдөй алат. Көрүү үчүн таптап коюңуз."</string>
     <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Билдирүү (<xliff:g id="MESSAGE">%1$s</xliff:g>) которулду."</string>
     <string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Билдирүү <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> тилинен <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> тилине которулду."</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index a172576..09b8bfc 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"ການ​ເບິ່ງ​ເຕັມ​ໜ້າ​ຈໍ"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"ຫາກຕ້ອງການອອກ, ໃຫ້ຮູດຈາກທາງເທິງລົງມາທາງລຸ່ມ."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"ໄດ້​ແລ້ວ"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"ແລ້ວໆ"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"ໂຕໝຸນປັບຊົ່ວໂມງ"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"ໂຕໝຸນປັບນາທີ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 6da22ed..8f5e7fb 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1842,6 +1842,8 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Peržiūrima viso ekrano režimu"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Jei norite išeiti, perbraukite žemyn iš viršaus."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Supratau"</string>
+    <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Pasukite, kad geriau matytumėte vaizdą"</string>
+    <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Išeikite iš išskaidyto ekrano režimo, kad geriau matytumėte vaizdą"</string>
     <string name="done_label" msgid="7283767013231718521">"Atlikta"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Apskritas valandų šliaužiklis"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Apskritas minučių šliaužiklis"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index aeb89a2..4edf968 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Skatīšanās pilnekrāna režīmā"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Lai izietu, no augšdaļas velciet lejup."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Labi"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Gatavs"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Stundu apļveida slīdnis"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Minūšu apļveida slīdnis"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index b0a2bbd..dbbd585 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -328,20 +328,20 @@
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"пристапува до податоците од сензорите за виталните знаци"</string>
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"Известувања"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"да прикажува известувања"</string>
-    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Преземе содржина на прозорец"</string>
-    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Ја следи содржината на прозорецот со кој се комуницира."</string>
-    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Вклучи „Истражувај со допир“"</string>
-    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Допрените ставки ќе се изговорат на глас и екранот може да се истражува со движења."</string>
-    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Го следи напишаниот текст"</string>
-    <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Опфаќа лични податоци како што се броеви на кредитни картички и лозинки."</string>
-    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Го контролира зголемувањето на екранот"</string>
-    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Го контролира нивото на зумирање и позиционирање на екранот."</string>
+    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"да ги вчитува содржините од прозорците"</string>
+    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"да ги проверува содржините од прозорецот што го користите"</string>
+    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"да вклучи „Истражувај со допир“"</string>
+    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"допрените ставки ќе се изговараат наглас и екранот ќе може да се истражува со движења"</string>
+    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"да го следи текстот што го пишувате"</string>
+    <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"вклучително и лични податоци како што се броеви на кредитни картички и лозинки"</string>
+    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"да го контролира зголемувањето на екранот"</string>
+    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"да го контролира нивото на зумирање и позиционирањето на екранот"</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Користете движења"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Може да допрете, повлечете, штипнете и да користите други движења."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Движења за отпечатоци"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Може да сними движења што се направени на сензорот за отпечатоци на уредот."</string>
     <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Зачувување слика од екранот"</string>
-    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да направи слика од екранот."</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да зачува слика од екранот."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"оневозможи или измени статусна лента"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Дозволува апликацијата да ја оневозможи статусната лента или да додава или отстранува системски икони."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"да стане статусна лента"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Се прикажува на цел екран"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"За да излезете, повлечете одозгора надолу."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Сфатив"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Готово"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Приказ на часови во кружно движење"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Приказ на минути во кружно движење"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 1b7daec..b59c322 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"പൂർണ്ണ സ്‌ക്രീനിൽ കാണുന്നു"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"അവസാനിപ്പിക്കാൻ, മുകളിൽ നിന്ന് താഴോട്ട് സ്വൈപ്പ് ചെയ്യുക."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"മനസ്സിലായി"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"പൂർത്തിയാക്കി"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"ചാക്രികമായി മണിക്കൂറുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"ചാക്രികമായി മിനിറ്റുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index dd6b40d..029358b 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Бүтэн дэлгэцээр үзэж байна"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Гарахаар бол дээрээс нь доош нь чирнэ үү."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Ойлголоо"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Дууссан"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Цаг гүйлгэгч"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Минут гүйлгэгч"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 6be015a..ad22469 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"पूर्ण स्क्रीनवर पाहत आहात"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"बाहेर पडण्यासाठी, वरून खाली स्वाइप करा."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"समजले"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"पूर्ण झाले"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"तास परिपत्रक स्लायडर"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"मिनिटे परिपत्रक स्लायडर"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 8ac7373..c038505 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Melihat skrin penuh"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Untuk keluar, leret dari atas ke bawah."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Faham"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Selesai"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Penggelangsar bulatan jam"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Penggelangsar bulatan minit"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 8603b28..badd048 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"မျက်နှာပြင်အပြည့် ကြည့်နေသည်"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"ထွက်ရန် အပေါ်မှ အောက်သို့ ဆွဲချပါ။"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"ရပါပြီ"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"ပြီးပါပြီ"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"နာရီရွေးချက်စရာ"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"မိနစ်လှည့်သော ရွေ့လျားတန်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index fe9f915..023e48f 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visning i fullskjerm"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Sveip ned fra toppen for å avslutte."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Skjønner"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Ferdig"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Sirkulær glidebryter for timer"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Sirkulær glidebryter for minutter"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index e21110e..f789ed6 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -334,8 +334,8 @@
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"ट्याप गरिएका वस्तुहरू चर्को स्वरमा बोलिने छन् र इसाराहरूको प्रयोग गरेर स्क्रिनमा अन्वेषण गर्न सकिन्छ।"</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"आफुले टाइप गरेको पाठको निरीक्षण गर्नुहोस्"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"व्यक्तिगत डेटा जस्तै क्रेडिट कार्ड नम्बरहरू र पासवर्डहरू समावेश गर्दछ।"</string>
-    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"प्रदर्शन आवर्धन नियन्त्रण गर्नुहोस्"</string>
-    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"प्रदर्शनको जुम स्तर र स्थिति नियन्त्रण गर्नुहोस्।"</string>
+    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"डिस्प्ले म्याग्निफिकेसन नियन्त्रण गर्नुहोस्"</string>
+    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"डिस्प्लेको जुम लेबल र स्थिति नियन्त्रण गर्नुहोस्।"</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"इसाराहरू सम्बन्धी कार्य गर्नुहोस्"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ट्याप, स्वाइप गर्न, थिच्न र अन्य इसाराहरू सम्बन्धी कार्य गर्न सक्छ"</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"फिंगरप्रिन्टका इसाराहरू"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"पूरा पर्दा हेर्दै"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"बाहिर निस्कन, माथिबाट तल स्वाइप गर्नुहोस्।"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"बुझेँ"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"भयो"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"घन्टा गोलाकार स्लाइडर"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"मिनेट गोलाकार स्लाइडर"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 1f380e1..7cc02fe 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -231,7 +231,7 @@
     <string name="shutdown_progress" msgid="5017145516412657345">"Uitzetten…"</string>
     <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"Je tablet wordt uitgezet."</string>
     <string name="shutdown_confirm" product="tv" msgid="7975942887313518330">"Je Android TV-apparaat wordt uitgezet."</string>
-    <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"Je horloge wordt uitgezet."</string>
+    <string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"Je smartwatch wordt uitgezet."</string>
     <string name="shutdown_confirm" product="default" msgid="136816458966692315">"Je telefoon wordt uitgezet."</string>
     <string name="shutdown_confirm_question" msgid="796151167261608447">"Wil je afsluiten?"</string>
     <string name="reboot_safemode_title" msgid="5853949122655346734">"Opnieuw opstarten in veilige modus"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Volledig scherm wordt getoond"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Swipe omlaag vanaf de bovenkant van het scherm om af te sluiten."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Ik snap het"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Klaar"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Ronde schuifregelaar voor uren"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Ronde schuifregelaar voor minuten"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 83cb868..b8f017e 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1077,13 +1077,13 @@
     <string name="menu_space_shortcut_label" msgid="5949311515646872071">"ସ୍ପେସ୍‍"</string>
     <string name="menu_enter_shortcut_label" msgid="6709499510082897320">"ଏଣ୍ଟର୍"</string>
     <string name="menu_delete_shortcut_label" msgid="4365787714477739080">"ଡିଲିଟ୍‌ କରନ୍ତୁ"</string>
-    <string name="search_go" msgid="2141477624421347086">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
-    <string name="search_hint" msgid="455364685740251925">"ସନ୍ଧାନ…"</string>
-    <string name="searchview_description_search" msgid="1045552007537359343">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+    <string name="search_go" msgid="2141477624421347086">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
+    <string name="search_hint" msgid="455364685740251925">"ସର୍ଚ୍ଚ କରନ୍ତୁ…"</string>
+    <string name="searchview_description_search" msgid="1045552007537359343">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
     <string name="searchview_description_query" msgid="7430242366971716338">"କ୍ୱେରୀ ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
     <string name="searchview_description_clear" msgid="1989371719192982900">"କ୍ୱେରୀ ଖାଲି କରନ୍ତୁ"</string>
     <string name="searchview_description_submit" msgid="6771060386117334686">"କ୍ୱେରୀ ଦାଖଲ କରନ୍ତୁ"</string>
-    <string name="searchview_description_voice" msgid="42360159504884679">"ଭଏସ୍‍ ସର୍ଚ୍ଚ"</string>
+    <string name="searchview_description_voice" msgid="42360159504884679">"ଭଏସ ସର୍ଚ୍ଚ"</string>
     <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍‍ କରନ୍ତୁ’ ସକ୍ଷମ କରିବେ?"</string>
     <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ସକ୍ଷମ କରିବାକୁ ଚାହେଁ। ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ଅନ୍‌ ଥିବାବେଳେ, ଆପଣଙ୍କ ଆଙ୍ଗୁଠି ତଳେ କ’ଣ ଅଛି, ତାହାର ବ୍ୟାଖ୍ୟା ଦେଖିପାରିବେ କିମ୍ବା ଟାବ୍‍ଲେଟ୍‍ ସହ କଥାବାର୍ତ୍ତା କରିବାକୁ ଜେଶ୍ଚର୍‌ କରିପାରିବେ।"</string>
     <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ସକ୍ଷମ କରିବାକୁ ଚାହେଁ। ’ସ୍ପର୍ଶ କରି ଏକ୍ସପ୍ଲୋର୍ କରନ୍ତୁ’ ଅନ୍‌ ଥିବାବେଳେ, ଆପଣଙ୍କ ଆଙ୍ଗୁଠି ତଳେ କ’ଣ ଅଛି, ତାହାର ବ୍ୟାଖ୍ୟା ଦେଖିପାରିବେ କିମ୍ବା ଫୋନ୍‍ ସହ କଥାବାର୍ତ୍ତା କରିବାକୁ ଜେଶ୍ଚର୍‌ କରିପାରିବେ।"</string>
@@ -1463,7 +1463,7 @@
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ଜୁମ୍ ନିୟନ୍ତ୍ରଣ ପାଇଁ ଦୁଇଥର ଟାପ୍‌ କରନ୍ତୁ"</string>
     <string name="gadget_host_error_inflating" msgid="2449961590495198720">"ୱିଜେଟ୍‍ ଯୋଡ଼ିପାରିବ ନାହିଁ।"</string>
     <string name="ime_action_go" msgid="5536744546326495436">"ଯାଆନ୍ତୁ"</string>
-    <string name="ime_action_search" msgid="4501435960587287668">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+    <string name="ime_action_search" msgid="4501435960587287668">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
     <string name="ime_action_send" msgid="8456843745664334138">"ପଠାନ୍ତୁ"</string>
     <string name="ime_action_next" msgid="4169702997635728543">"ପରବର୍ତ୍ତୀ"</string>
     <string name="ime_action_done" msgid="6299921014822891569">"ହୋଇଗଲା"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ଦେଖାଯାଉଛି"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"ବାହାରିବା ପାଇଁ, ଉପରୁ ତଳକୁ ସ୍ୱାଇପ୍‍ କରନ୍ତୁ।"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"ବୁଝିଗଲି"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"ହୋଇଗଲା"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"ଘଣ୍ଟା ସର୍କୁଲାର୍‍ ସ୍ଲାଇଡର୍‍"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"ମିନିଟ୍ସ ସର୍କୁଲାର୍‍ ସ୍ଲାଇଡର୍‍"</string>
@@ -1931,7 +1935,7 @@
     <string name="language_picker_regions_section_suggested" msgid="6080131515268225316">"ପ୍ରସ୍ତାବିତ"</string>
     <string name="language_picker_section_all" msgid="1985809075777564284">"ସମସ୍ତ ଭାଷା"</string>
     <string name="region_picker_section_all" msgid="756441309928774155">"ସମସ୍ତ ଅଞ୍ଚଳ"</string>
-    <string name="locale_search_menu" msgid="6258090710176422934">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+    <string name="locale_search_menu" msgid="6258090710176422934">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
     <string name="app_suspended_title" msgid="888873445010322650">"ଆପ୍‌ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="app_suspended_default_message" msgid="6451215678552004172">"ବର୍ତ୍ତମାନ <xliff:g id="APP_NAME_0">%1$s</xliff:g> ଉପଲବ୍ଧ ନାହିଁ। ଏହା <xliff:g id="APP_NAME_1">%2$s</xliff:g> ଦ୍ଵାରା ପରିଚାଳିତ ହେଉଛି।"</string>
     <string name="app_suspended_more_details" msgid="211260942831587014">"ଅଧିକ ଜାଣନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 1cb12c2..66aca4f 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -328,7 +328,7 @@
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"ਆਪਣੇ ਸਰੀਰ ਦੇ ਅਹਿਮ ਚਿੰਨ੍ਹਾਂ ਬਾਰੇ ਸੰਵੇਦਕ ਡਾਟਾ ਤੱਕ ਪਹੁੰਚ ਕਰਨ"</string>
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"ਸੂਚਨਾਵਾਂ"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"ਸੂਚਨਾਵਾਂ ਦਿਖਾਓ"</string>
-    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ਵਿੰਡੋ ਸਮੱਗਰੀ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨਾ"</string>
+    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"ਵਿੰਡੋ ਸਮੱਗਰੀ ਮੁੜ-ਪ੍ਰਾਪਤ ਕਰਨਾ"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"ਉਸ ਵਿੰਡੋ ਸਮੱਗਰੀ ਦੀ ਜਾਂਚ ਕਰੋ, ਜਿਸ ਨਾਲ ਤੁਸੀਂ ਅੰਤਰਕਿਰਿਆ ਕਰ ਰਹੇ ਹੋ"</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"\'ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ\' ਚਾਲੂ ਕਰਨਾ"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"ਟੈਪ ਕੀਤੀਆਂ ਆਈਟਮਾਂ ਨੂੰ ਉੱਚੀ ਆਵਾਜ਼ ਵਿੱਚ ਬੋਲਿਆ ਜਾਵੇਗਾ ਅਤੇ ਸਕ੍ਰੀਨ ਦੀ ਸੰਕੇਤਾਂ ਦੀ ਵਰਤੋਂ ਨਾਲ ਪੜਚੋਲ ਕੀਤੀ ਜਾ ਸਕਦੀ ਹੈ।"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦੇਖੋ"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"ਬਾਹਰ ਜਾਣ ਲਈ, ਉਪਰੋਂ ਹੇਠਾਂ ਸਵਾਈਪ ਕਰੋ।"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"ਸਮਝ ਲਿਆ"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"ਹੋ ਗਿਆ"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"ਘੰਟੇ ਸਰਕੁਲਰ ਸਲਾਈਡਰ"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"ਮਿੰਟ ਸਰਕੁਲਰ ਸਲਾਈਡਰ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index a2c06b7..61e3217 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1842,6 +1842,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Włączony pełny ekran"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Aby wyjść, przesuń palcem z góry na dół."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Gotowe"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Kołowy suwak godzin"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Kołowy suwak minut"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 42a477b..91f0a07 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1534,7 +1534,7 @@
     <string name="add_account_button_label" msgid="322390749416414097">"Adicionar conta"</string>
     <string name="number_picker_increment_button" msgid="7621013714795186298">"Aumentar"</string>
     <string name="number_picker_decrement_button" msgid="5116948444762708204">"Diminuir"</string>
-    <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> toque e mantenha pressionado."</string>
+    <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> toque e pressione."</string>
     <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Deslize para cima para aumentar e para baixo para diminuir."</string>
     <string name="time_picker_increment_minute_button" msgid="7195870222945784300">"Aumentar minuto"</string>
     <string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Diminuir minuto"</string>
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visualização em tela cheia"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize de cima para baixo."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendi"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Concluído"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Controle deslizante circular das horas"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Controle deslizante circular dos minutos"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 6d7f391..03bc705 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visualização de ecrã inteiro"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Concluído"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Controlo de deslize circular das horas"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Controlo de deslize circular dos minutos"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 42a477b..91f0a07 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1534,7 +1534,7 @@
     <string name="add_account_button_label" msgid="322390749416414097">"Adicionar conta"</string>
     <string name="number_picker_increment_button" msgid="7621013714795186298">"Aumentar"</string>
     <string name="number_picker_decrement_button" msgid="5116948444762708204">"Diminuir"</string>
-    <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> toque e mantenha pressionado."</string>
+    <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> toque e pressione."</string>
     <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Deslize para cima para aumentar e para baixo para diminuir."</string>
     <string name="time_picker_increment_minute_button" msgid="7195870222945784300">"Aumentar minuto"</string>
     <string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Diminuir minuto"</string>
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visualização em tela cheia"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize de cima para baixo."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Entendi"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Concluído"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Controle deslizante circular das horas"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Controle deslizante circular dos minutos"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 83fba3f..38ba48d 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1841,6 +1841,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Vizualizare pe ecran complet"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Pentru a ieși, glisează de sus în jos."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Am înțeles"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Terminat"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Selector circular pentru ore"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Selector circular pentru minute"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 713e67d..93cbae2 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1842,6 +1842,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Полноэкранный режим"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Чтобы выйти, проведите по экрану сверху вниз."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"ОК"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Готово"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Выбор часов на циферблате"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Выбор минут на циферблате"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 8605c67..d79d0b8 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"මුළු තිරය බලමින්"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"ඉවත් වීමට, ඉහළ සිට පහළට ස්වයිප් කරන්න"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"වැටහුණි"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"අවසන්"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"පැය කවාකාර සර්පනය"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"මිනිත්තු කවාකාර සර්පනය"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 01e57e1..092827f 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -335,7 +335,7 @@
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Zapnúť funkciu Preskúmanie dotykom"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Po klepnutí na položku sa vysloví jej názov a obrazovku je možné preskúmať pomocou gest."</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Sledovať zadávaný text"</string>
-    <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Sledovanie zahŕňa osobné údaje ako sú čísla kreditných kariet a heslá."</string>
+    <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Sledovanie zahŕňa osobné údaje, ako sú čísla kreditných kariet a heslá."</string>
     <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Ovládať priblíženie obrazovky"</string>
     <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Ovládajte umiestnenie a úroveň priblíženia obrazovky."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Gestá"</string>
@@ -1842,6 +1842,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Zobrazenie na celú obrazovku"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Ukončíte potiahnutím zhora nadol."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Dobre"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Hotovo"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Kruhový posúvač hodín"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Kruhový posúvač minút"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index e0026a6..f557823 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1842,6 +1842,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Vklopljen je celozaslonski način"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Zaprete ga tako, da z vrha s prstom povlečete navzdol."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Razumem"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Dokončano"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Okrogli drsnik za ure"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Okrogli drsnik za minute"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 7a9f8a3..7875ea2 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -328,20 +328,20 @@
     <string name="permgroupdesc_sensors" msgid="2610631290633747752">"qasu tek të dhënat e sensorëve rreth shenjave të tua jetësore"</string>
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"Njoftimet"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"shfaq njoftimet"</string>
-    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Nxjerrë përmbajtjen e dritares"</string>
+    <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Të nxjerrë përmbajtjen e dritares"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspekton përmbajtjen e dritares me të cilën po ndërvepron."</string>
-    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Aktivizojë funksionin \"Eksploro me prekje\""</string>
+    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Të aktivizojë veçorinë \"Eksploro me prekje\""</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Artikujt e trokitur do të lexohen me zë të lartë dhe ekrani mund të eksplorohet duke përdorur gjestet."</string>
-    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Vëzhgojë tekstin që shkruan"</string>
+    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Të vëzhgojë tekstin që shkruan"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Përfshin të dhëna personale, si numrat e kartave të kreditit dhe fjalëkalimet."</string>
-    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Kontrollo zmadhimin e ekranit"</string>
-    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrollo nivelin dhe pozicionimin e zmadhimit të ekranit."</string>
+    <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Të kontrollojë zmadhimin e ekranit"</string>
+    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Kontrollon nivelin dhe pozicionimin e zmadhimit të ekranit."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Kryen gjeste"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Mund të trokasë, rrëshqasë, bashkojë gishtat dhe kryejë gjeste të tjera."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gjestet e gjurmës së gishtit"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Mund të regjistrojë gjestet e kryera në sensorin e gjurmës së gishtit të pajisjes."</string>
     <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Nxirr një pamje të ekranit"</string>
-    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Mund të nxirret një pamje e ekranit."</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Mund të nxjerrë një pamje e ekranit."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"çaktivizo ose modifiko shiritin e statusit"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Lejon aplikacionin të çaktivizojë shiritin e statusit dhe të heqë ikonat e sistemit."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"të bëhet shiriti i statusit"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Po shikon ekranin e plotë"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Për të dalë, rrëshqit nga lart poshtë."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"E kuptova"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"U krye"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Rrëshqitësi rrethor i orëve"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Rrëshqitësi rrethor i minutave"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index f606c8f2c..bf8c0f7 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1841,6 +1841,8 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Приказује се цео екран"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Да бисте изашли, превуците надоле одозго."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Важи"</string>
+    <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Ротирајте ради бољег приказа"</string>
+    <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Изађите из подељеног екрана ради бољег приказа"</string>
     <string name="done_label" msgid="7283767013231718521">"Готово"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Кружни клизач за сате"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Кружни клизач за минуте"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index fb9a313..7e920d8 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -341,7 +341,7 @@
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingeravtrycksrörelser"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Kan registrera rörelser som utförs med hjälp av enhetens fingeravtryckssensor."</string>
     <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ta skärmbild"</string>
-    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Du kan ta en skärmbild av skärmen."</string>
+    <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Kan ta en skärmbild av skärmen."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"inaktivera eller ändra statusfält"</string>
     <string name="permdesc_statusBar" msgid="5809162768651019642">"Tillåter att appen inaktiverar statusfältet eller lägger till och tar bort systemikoner."</string>
     <string name="permlab_statusBarService" msgid="2523421018081437981">"visas i statusfältet"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Visar på fullskärm"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Svep nedåt från skärmens överkant för att avsluta."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Klart"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Cirkelreglage för timmar"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Cirkelreglage för minuter"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 180293f..d380436 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Unatazama skrini nzima"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Ili kuondoka, telezesha kidole kutoka juu hadi chini."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Nimeelewa"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Imekamilika"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Kitelezi cha mviringo wa saa"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Kitelezi cha mviringo wa dakika"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 186dab3..aee945d 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -332,7 +332,7 @@
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"நீங்கள் பணியாற்றிக் கொண்டிருக்கும் சாளரத்தின் உள்ளடக்கத்தைப் பார்க்கலாம்."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"தொடுவதன் மூலம் அறிவதை இயக்கும்"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"தட்டிய உள்ளடக்கம் சத்தமாகப் படிக்கப்படும், சைகைகளைப் பயன்படுத்தி திரையில் உலாவலாம்."</string>
-    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"நீங்கள் தட்டச்சு செய்யும் உரையைக் கவனிக்கும்"</string>
+    <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"நீங்கள் டைப் செய்யும் வார்த்தையைக் கவனிக்கும்."</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"கிரெடிட் கார்டு எண்கள் மற்றும் கடவுச்சொற்கள் போன்ற தனிப்பட்ட தகவலும் உள்ளடங்கும்."</string>
     <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"திரை பெரிதாவதைக் கட்டுப்படுத்தும்"</string>
     <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"திரையின் ஜூம் அளவையும் நிலையையும் கட்டுப்படுத்தலாம்."</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"முழுத் திரையில் காட்டுகிறது"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"வெளியேற, மேலிருந்து கீழே ஸ்வைப் செய்யவும்"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"புரிந்தது"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"முடிந்தது"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"மணிநேர வட்ட வடிவ ஸ்லைடர்"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"நிமிடங்களுக்கான வட்டவடிவ ஸ்லைடர்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 7cebe27..7d6b3cd 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -330,7 +330,7 @@
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"నోటిఫికేషన్‌లను చూపండి"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"విండో కంటెంట్‍ను తిరిగి పొందుతుంది"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"మీరు పరస్పర చర్య చేస్తున్న విండో కంటెంట్‌‍ను పరిశీలిస్తుంది."</string>
-    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"తాకడం ద్వారా విశ్లేషణను ప్రారంభిస్తుంది"</string>
+    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"తాకడం ద్వారా విశ్లేషణను ఆన్ చేయండి"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"నొక్కిన అంశాలు బిగ్గరగా చదివి వినిపించబడతాయి మరియు సంజ్ఞలను ఉపయోగించి స్క్రీన్‌ను విశ్లేషించవచ్చు."</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"మీరు టైప్ చేస్తున్న వచనాన్ని పరిశీలిస్తుంది"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"క్రెడిట్ కార్డు నంబర్‌లు మరియు పాస్‌వర్డ్‌ల వంటి వ్యక్తిగత డేటాను కలిగి ఉంటుంది."</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"ఫుల్-స్క్రీన్‌లో వీక్షిస్తున్నారు"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"నిష్క్రమించడానికి, పై నుండి క్రిందికి స్వైప్ చేయండి."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"అర్థమైంది"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"పూర్తయింది"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"గంటల వృత్తాకార స్లయిడర్"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"నిమిషాల వృత్తాకార స్లయిడర్"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index a14b574..852aef0 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"กำลังดูแบบเต็มหน้าจอ"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"หากต้องการออก ให้เลื่อนลงจากด้านบน"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"รับทราบ"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"เสร็จสิ้น"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"ตัวเลื่อนหมุนระบุชั่วโมง"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"ตัวเลื่อนหมุนระบุนาที"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 349a39b..7540fcd 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Panonood sa full screen"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Upang lumabas, mag-swipe mula sa itaas pababa."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Nakuha ko"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Tapos na"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Pabilog na slider ng mga oras"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Pabilog na slider ng mga minuto"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 4e8c5ef..10501d6 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Tam ekran olarak görüntüleme"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Çıkmak için yukarıdan aşağıya doğru hızlıca kaydırın."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Anladım"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Bitti"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Saat kaydırma çemberi"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Dakika kaydırma çemberi"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 6f63a2c..eecc6e2 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -333,7 +333,7 @@
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Отримувати вміст вікна"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Перевіряти вміст вікна, з яким ви взаємодієте."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Увімкнути функцію дослідження дотиком"</string>
-    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Активувати голосові підказки для елементів, яких торкаються, і користуватися інтерфейсом за допомогою жестів."</string>
+    <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Озвучувати елементи, яких торкаються, і здійснювати навігацію екраном за допомогою жестів."</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Переглядати текст, який ви вводите"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Включає особисті дані, як-от номери кредитних карток і паролі."</string>
     <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Контролювати збільшення екрана"</string>
@@ -1842,6 +1842,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Перегляд на весь екран"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Щоб вийти, проведіть пальцем зверху вниз."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Готово"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Вибір годин на циферблаті"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Вибір хвилин на циферблаті"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index e3783c3..feea70c 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"پوری اسکرین میں دیکھ رہے ہیں"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"خارج ہونے کیلئے اوپر سے نیچے سوائپ کریں۔"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"سمجھ آ گئی"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"ہو گیا"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"گھنٹوں کا سرکلر سلائیڈر"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"منٹس سرکلر سلائیڈر"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 4a23c25..8d3bedf 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1840,6 +1840,8 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Butun ekranli rejim"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Chiqish uchun tepadan pastga torting."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Yaxshiroq koʻrish uchun kamerani buring"</string>
+    <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Yaxshiroq koʻrish uchun ajratilgan ekran rejimidan chiqing"</string>
     <string name="done_label" msgid="7283767013231718521">"Tayyor"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Doiradan soatni tanlang"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Doiradan daqiqani tanlang"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 93d907e..05acca6 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -330,7 +330,7 @@
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"hiển thị thông báo"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Truy xuất nội dung cửa sổ"</string>
     <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Kiểm tra nội dung của cửa sổ bạn đang tương tác."</string>
-    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Bật Khám phá bằng cách chạm"</string>
+    <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Bật tính năng Khám phá bằng cách chạm"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Đọc to các mục được nhấn và cho phép khám phá màn hình bằng cử chỉ."</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Quan sát nội dung bạn nhập"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Bao gồm dữ liệu cá nhân chẳng hạn như số thẻ tín dụng và mật khẩu."</string>
@@ -796,7 +796,7 @@
   <string-array name="phoneTypes">
     <item msgid="8996339953292723951">"Nhà riêng"</item>
     <item msgid="7740243458912727194">"Di động"</item>
-    <item msgid="8526146065496663766">"Cơ quan"</item>
+    <item msgid="8526146065496663766">"Nơi làm việc"</item>
     <item msgid="8150904584178569699">"Số fax cơ quan"</item>
     <item msgid="4537253139152229577">"Số fax nhà riêng"</item>
     <item msgid="6751245029698664340">"Số máy nhắn tin"</item>
@@ -805,24 +805,24 @@
   </string-array>
   <string-array name="emailAddressTypes">
     <item msgid="7786349763648997741">"Nhà riêng"</item>
-    <item msgid="435564470865989199">"Cơ quan"</item>
+    <item msgid="435564470865989199">"Nơi làm việc"</item>
     <item msgid="4199433197875490373">"Khác"</item>
     <item msgid="3233938986670468328">"Tùy chỉnh"</item>
   </string-array>
   <string-array name="postalAddressTypes">
     <item msgid="3861463339764243038">"Nhà riêng"</item>
-    <item msgid="5472578890164979109">"Cơ quan"</item>
+    <item msgid="5472578890164979109">"Nơi làm việc"</item>
     <item msgid="5718921296646594739">"Khác"</item>
     <item msgid="5523122236731783179">"Tùy chỉnh"</item>
   </string-array>
   <string-array name="imAddressTypes">
     <item msgid="588088543406993772">"Nhà riêng"</item>
-    <item msgid="5503060422020476757">"Cơ quan"</item>
+    <item msgid="5503060422020476757">"Nơi làm việc"</item>
     <item msgid="2530391194653760297">"Khác"</item>
     <item msgid="7640927178025203330">"Tùy chỉnh"</item>
   </string-array>
   <string-array name="organizationTypes">
-    <item msgid="6144047813304847762">"Cơ quan"</item>
+    <item msgid="6144047813304847762">"Nơi làm việc"</item>
     <item msgid="7402720230065674193">"Khác"</item>
     <item msgid="808230403067569648">"Tùy chỉnh"</item>
   </string-array>
@@ -839,7 +839,7 @@
     <string name="phoneTypeCustom" msgid="5120365721260686814">"Tùy chỉnh"</string>
     <string name="phoneTypeHome" msgid="3880132427643623588">"Nhà riêng"</string>
     <string name="phoneTypeMobile" msgid="1178852541462086735">"Di động"</string>
-    <string name="phoneTypeWork" msgid="6604967163358864607">"Cơ quan"</string>
+    <string name="phoneTypeWork" msgid="6604967163358864607">"Nơi làm việc"</string>
     <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Số fax cơ quan"</string>
     <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Số fax nhà riêng"</string>
     <string name="phoneTypePager" msgid="576402072263522767">"Số máy nhắn tin"</string>
@@ -863,16 +863,16 @@
     <string name="eventTypeOther" msgid="530671238533887997">"Khác"</string>
     <string name="emailTypeCustom" msgid="1809435350482181786">"Tùy chỉnh"</string>
     <string name="emailTypeHome" msgid="1597116303154775999">"Nhà riêng"</string>
-    <string name="emailTypeWork" msgid="2020095414401882111">"Cơ quan"</string>
+    <string name="emailTypeWork" msgid="2020095414401882111">"Nơi làm việc"</string>
     <string name="emailTypeOther" msgid="5131130857030897465">"Khác"</string>
     <string name="emailTypeMobile" msgid="787155077375364230">"Di Động"</string>
     <string name="postalTypeCustom" msgid="5645590470242939129">"Tùy chỉnh"</string>
     <string name="postalTypeHome" msgid="7562272480949727912">"Nhà riêng"</string>
-    <string name="postalTypeWork" msgid="8553425424652012826">"Cơ quan"</string>
+    <string name="postalTypeWork" msgid="8553425424652012826">"Nơi làm việc"</string>
     <string name="postalTypeOther" msgid="7094245413678857420">"Khác"</string>
     <string name="imTypeCustom" msgid="5653384545085765570">"Tùy chỉnh"</string>
     <string name="imTypeHome" msgid="6996507981044278216">"Nhà riêng"</string>
-    <string name="imTypeWork" msgid="2099668940169903123">"Cơ quan"</string>
+    <string name="imTypeWork" msgid="2099668940169903123">"Nơi làm việc"</string>
     <string name="imTypeOther" msgid="8068447383276219810">"Khác"</string>
     <string name="imProtocolCustom" msgid="4437878287653764692">"Tùy chỉnh"</string>
     <string name="imProtocolAim" msgid="4050198236506604378">"AIM"</string>
@@ -884,7 +884,7 @@
     <string name="imProtocolIcq" msgid="2410325380427389521">"ICQ"</string>
     <string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
     <string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
-    <string name="orgTypeWork" msgid="8684458700669564172">"Cơ quan"</string>
+    <string name="orgTypeWork" msgid="8684458700669564172">"Nơi làm việc"</string>
     <string name="orgTypeOther" msgid="5450675258408005553">"Khác"</string>
     <string name="orgTypeCustom" msgid="1126322047677329218">"Tùy chỉnh"</string>
     <string name="relationTypeCustom" msgid="282938315217441351">"Tùy chỉnh"</string>
@@ -904,7 +904,7 @@
     <string name="relationTypeSpouse" msgid="6916682664436031703">"Vợ/chồng"</string>
     <string name="sipAddressTypeCustom" msgid="6283889809842649336">"Tùy chỉnh"</string>
     <string name="sipAddressTypeHome" msgid="5918441930656878367">"Nhà riêng"</string>
-    <string name="sipAddressTypeWork" msgid="7873967986701216770">"Cơ quan"</string>
+    <string name="sipAddressTypeWork" msgid="7873967986701216770">"Nơi làm việc"</string>
     <string name="sipAddressTypeOther" msgid="6317012577345187275">"Khác"</string>
     <string name="quick_contacts_not_available" msgid="1262709196045052223">"Không tìm thấy ứng dụng nào để xem liên hệ này."</string>
     <string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"Nhập mã PIN"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Xem toàn màn hình"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Để thoát, hãy vuốt từ trên cùng xuống dưới."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Xong"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Thanh trượt giờ hình tròn"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Thanh trượt phút hình tròn"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 1c419fc..604e09a 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"目前处于全屏模式"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"要退出,请从顶部向下滑动。"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"知道了"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"完成"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"小时转盘"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"分钟转盘"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index cc4d15e..85124a4 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"開啟全螢幕"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"由頂部向下滑動即可退出。"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"知道了"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"完成"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"小時環形滑桿"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"分鐘環形滑桿"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 810ddc6..9207ce2 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -284,7 +284,7 @@
     <string name="notification_channel_vpn" msgid="1628529026203808999">"VPN 狀態"</string>
     <string name="notification_channel_device_admin" msgid="6384932669406095506">"來自 IT 管理員的快訊"</string>
     <string name="notification_channel_alerts" msgid="5070241039583668427">"快訊"</string>
-    <string name="notification_channel_retail_mode" msgid="3732239154256431213">"零售商示範模式"</string>
+    <string name="notification_channel_retail_mode" msgid="3732239154256431213">"零售商展示模式"</string>
     <string name="notification_channel_usb" msgid="1528280969406244896">"USB 連線"</string>
     <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"應用程式執行中"</string>
     <string name="notification_channel_foreground_service" msgid="7102189948158885178">"正在耗用電量的應用程式"</string>
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"以全螢幕檢視"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"如要退出,請從畫面頂端向下滑動。"</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"知道了"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"完成"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"小時數環狀滑桿"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"分鐘數環狀滑桿"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index d40fd0a..5d5d14c 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1840,6 +1840,10 @@
     <string name="immersive_cling_title" msgid="2307034298721541791">"Ukubuka isikrini esigcwele"</string>
     <string name="immersive_cling_description" msgid="7092737175345204832">"Ukuze uphume, swayiphela phansi kusuka phezulu."</string>
     <string name="immersive_cling_positive" msgid="7047498036346489883">"Ngiyitholile"</string>
+    <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
+    <skip />
+    <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
+    <skip />
     <string name="done_label" msgid="7283767013231718521">"Kwenziwe"</string>
     <string name="hour_picker_description" msgid="5153757582093524635">"Amahora weslayidi esiyindingilizi"</string>
     <string name="minute_picker_description" msgid="9029797023621927294">"Amaminithi weslayidi esiyindingilizi"</string>
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 4b27bf2..fe296c7 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -18,7 +18,6 @@
     <bool name="kg_enable_camera_default_widget">true</bool>
     <bool name="kg_center_small_widgets_vertically">false</bool>
     <bool name="kg_top_align_page_shrink_on_bouncer_visible">true</bool>
-    <bool name="kg_wake_on_acquire_start">false</bool>
     <bool name="action_bar_embed_tabs">true</bool>
     <bool name="split_action_bar_is_narrow">true</bool>
     <bool name="preferences_prefer_dual_pane">false</bool>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index ea6e1f1..a99ba15 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -72,8 +72,8 @@
     <item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item>
     <item name="secondary_content_alpha_material_light" format="float" type="dimen">0.60</item>
 
-    <item name="highlight_alpha_material_light" format="float" type="dimen">0.10</item>
-    <item name="highlight_alpha_material_dark" format="float" type="dimen">0.10</item>
+    <item name="highlight_alpha_material_light" format="float" type="dimen">0.5</item>
+    <item name="highlight_alpha_material_dark" format="float" type="dimen">0.5</item>
     <item name="highlight_alpha_material_colored" format="float" type="dimen">0.10</item>
 
     <!-- Primary & accent colors -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2f5efd1..87ece55 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -576,6 +576,10 @@
          rotations as the default behavior. -->
     <bool name="config_allowAllRotations">false</bool>
 
+    <!-- If false and config_allowAllRotations is false, the screen will rotate to the natural
+         orientation of the device when the auto-rotate policy is toggled. -->
+    <bool name="config_useCurrentRotationOnRotationLockChange">false</bool>
+
     <!-- If true, the direction rotation is applied to get to an application's requested
          orientation is reversed.  Normally, the model is that landscape is
          clockwise from portrait; thus on a portrait device an app requesting
@@ -648,6 +652,16 @@
          The default is false. -->
     <bool name="config_lidControlsSleep">false</bool>
 
+    <!-- The device states (supplied by DeviceStateManager) that should be treated as open by the
+         device fold controller. Default is empty. -->
+    <integer-array name="config_openDeviceStates">
+        <!-- Example:
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        -->
+    </integer-array>
+
     <!-- The device states (supplied by DeviceStateManager) that should be treated as folded by the
          display fold controller. Default is empty. -->
     <integer-array name="config_foldedDeviceStates">
@@ -668,6 +682,16 @@
         -->
     </integer-array>
 
+    <!-- The device states (supplied by DeviceStateManager) that should be treated as a rear display
+     state. Default is empty. -->
+    <integer-array name="config_rearDisplayDeviceStates">
+        <!-- Example:
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        -->
+    </integer-array>
+
     <!-- Indicates whether the window manager reacts to half-fold device states by overriding
      rotation. -->
     <bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool>
@@ -960,6 +984,15 @@
     <!-- Boolean indicating whether light mode is allowed when DWB is turned on. -->
     <bool name="config_displayWhiteBalanceLightModeAllowed">true</bool>
 
+    <!-- Duration, in milliseconds, of the display white balance animated transitions. -->
+    <integer name="config_displayWhiteBalanceTransitionTime">3000</integer>
+
+    <!-- Device states where the sensor based rotation values should be reversed around the Z axis
+         for the default display.
+         TODO(b/265312193): Remove this workaround when this bug is fixed.-->
+    <integer-array name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis">
+    </integer-array>
+
     <!-- Indicate available ColorDisplayManager.COLOR_MODE_xxx. -->
     <integer-array name="config_availableColorModes">
         <!-- Example:
@@ -2074,6 +2107,9 @@
          STREAM_MUSIC as if it's on TV platform. -->
     <bool name="config_single_volume">false</bool>
 
+    <!-- Volume policy -->
+    <bool name="config_volume_down_to_enter_silent">false</bool>
+
     <!-- The number of volume steps for the notification stream -->
     <integer name="config_audio_notif_vol_steps">7</integer>
 
@@ -4972,9 +5008,8 @@
     <!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
     <bool name="config_faceAuthDismissesKeyguard">true</bool>
 
-    <!-- Default value for whether a SFPS device is required to be interactive for fingerprint auth
-    to unlock the device.  -->
-    <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+    <!-- Default value for performant auth feature. -->
+    <bool name="config_performantAuthDefault">false</bool>
 
     <!-- The component name for the default profile supervisor, which can be set as a profile owner
     even after user setup is complete. The defined component should be used for supervision purposes
@@ -5353,6 +5388,9 @@
     <!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. -->
     <bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool>
 
+    <!-- Whether using display aspect ratio as a default aspect ratio for all letterboxed apps. -->
+    <bool name="config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled">false</bool>
+
     <!-- Whether the specific behaviour for translucent activities letterboxing is enabled.
          TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
     <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9410e06..ba54183 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5154,6 +5154,14 @@
     <!-- Cling help message confirmation button when hiding the navigation bar entering immersive mode [CHAR LIMIT=30] -->
     <string name="immersive_cling_positive">Got it</string>
 
+    <!-- Text on a toast shown after the system rotates the screen for camera app
+         compatibility. [CHAR LIMIT=NONE] -->
+    <string name="display_rotation_camera_compat_toast_after_rotation">Rotate for a better view</string>
+
+    <!-- Text on a toast shown when a camera view is started within the app that may not be able
+         to display the camera preview correctly while in split screen. [CHAR LIMIT=NONE] -->
+    <string name="display_rotation_camera_compat_toast_in_split_screen">Exit split screen for a better view</string>
+
     <!-- Label for button to confirm chosen date or time [CHAR LIMIT=30] -->
     <string name="done_label">Done</string>
     <!--
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 41281fa..15c40c0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -321,6 +321,7 @@
   <java-symbol type="bool" name="config_use_strict_phone_number_comparation_for_kazakhstan" />
   <java-symbol type="integer" name="config_phonenumber_compare_min_match" />
   <java-symbol type="bool" name="config_single_volume" />
+  <java-symbol type="bool" name="config_volume_down_to_enter_silent" />
   <java-symbol type="bool" name="config_voice_capable" />
   <java-symbol type="bool" name="config_requireCallCapableAccountForHandle" />
   <java-symbol type="bool" name="config_user_notification_of_restrictied_mobile_access" />
@@ -1732,6 +1733,7 @@
   <java-symbol type="attr" name="dialogTitleDecorLayout" />
   <java-symbol type="attr" name="dialogTitleIconsDecorLayout" />
   <java-symbol type="bool" name="config_allowAllRotations" />
+  <java-symbol type="bool" name="config_useCurrentRotationOnRotationLockChange"/>
   <java-symbol type="bool" name="config_annoy_dianne" />
   <java-symbol type="bool" name="config_startDreamImmediatelyOnDock" />
   <java-symbol type="bool" name="config_carDockEnablesAccelerometer" />
@@ -2560,6 +2562,8 @@
   <java-symbol type="string" name="zen_mode_default_weekends_name" />
   <java-symbol type="string" name="zen_mode_default_events_name" />
   <java-symbol type="string" name="zen_mode_default_every_night_name" />
+  <java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
+  <java-symbol type="string" name="display_rotation_camera_compat_toast_in_split_screen" />
   <java-symbol type="array" name="config_system_condition_providers" />
   <java-symbol type="string" name="muted_by" />
   <java-symbol type="string" name="zen_mode_alarm" />
@@ -2724,7 +2728,7 @@
   <java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
   <java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
   <java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
-  <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
+  <java-symbol type="bool" name="config_performantAuthDefault" />
 
   <!-- Face config -->
   <java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
@@ -2839,7 +2843,6 @@
   <java-symbol type="dimen" name="fast_scroller_minimum_touch_target" />
   <java-symbol type="array" name="config_cdma_international_roaming_indicators" />
   <java-symbol type="string" name="kg_text_message_separator" />
-  <java-symbol type="bool" name="kg_wake_on_acquire_start" />
 
   <java-symbol type="bool" name="config_use_sim_language_file" />
   <java-symbol type="bool" name="config_LTE_eri_for_network_name" />
@@ -3434,6 +3437,12 @@
   <java-symbol type="array" name="config_displayWhiteBalanceDisplayPrimaries" />
   <java-symbol type="array" name="config_displayWhiteBalanceDisplayNominalWhite" />
   <java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" />
+  <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" />
+
+  <!-- Device states where the sensor based rotation values should be reversed around the Z axis
+       for the default display.
+       TODO(b/265312193): Remove this workaround when this bug is fixed.-->
+  <java-symbol type="array" name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis" />
 
   <!-- Default first user restrictions -->
   <java-symbol type="array" name="config_defaultFirstUserRestrictions" />
@@ -4013,8 +4022,10 @@
   <java-symbol type="integer" name="config_maxScanTasksForHomeVisibility" />
 
   <!-- For Foldables -->
+  <java-symbol type="array" name="config_openDeviceStates" />
   <java-symbol type="array" name="config_foldedDeviceStates" />
   <java-symbol type="array" name="config_halfFoldedDeviceStates" />
+  <java-symbol type="array" name="config_rearDisplayDeviceStates" />
   <java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" />
   <java-symbol type="array" name="config_deviceStatesOnWhichToWakeUp" />
   <java-symbol type="array" name="config_deviceStatesOnWhichToSleep" />
@@ -4462,6 +4473,7 @@
   <java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
   <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
   <java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
+  <java-symbol type="bool" name="config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled" />
   <java-symbol type="bool" name="config_isCompatFakeFocusEnabled" />
   <java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
   <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index ed2b101..3768063 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -368,4 +368,20 @@
             PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
         assertEquals(n1, "cache_key.bluetooth.get_state");
     }
+
+    @Test
+    public void testOnTrimMemory() {
+        TestCache cache = new TestCache(MODULE, "trimMemoryTest");
+        // The cache is not active until it has been invalidated once.
+        cache.invalidateCache();
+        // Populate the cache with six entries.
+        for (int i = 0; i < 6; i++) {
+            cache.query(i);
+        }
+        // The maximum number of entries in TestCache is 4, so even though six entries were
+        // created, only four are retained.
+        assertEquals(4, cache.size());
+        PropertyInvalidatedCache.onTrimMemory();
+        assertEquals(0, cache.size());
+    }
 }
diff --git a/core/tests/screenshothelpertests/Android.bp b/core/tests/screenshothelpertests/Android.bp
index 37af99c..3c71e6e 100644
--- a/core/tests/screenshothelpertests/Android.bp
+++ b/core/tests/screenshothelpertests/Android.bp
@@ -13,7 +13,7 @@
     srcs: [
         "src/**/*.java",
     ],
-    
+
     static_libs: [
         "frameworks-base-testutils",
         "androidx.test.runner",
@@ -21,6 +21,7 @@
         "androidx.test.ext.junit",
         "mockito-target-minus-junit4",
         "platform-test-annotations",
+        "testng",
     ],
 
     libs: [
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 2719431..5c9894e 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.util;
 
 import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
 
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.fail;
@@ -31,9 +32,11 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
 import android.graphics.Insets;
 import android.graphics.Rect;
-import android.os.Bundle;
+import android.hardware.HardwareBuffer;
 import android.os.Handler;
 import android.os.Looper;
 import android.view.WindowManager;
@@ -79,30 +82,48 @@
 
     @Test
     public void testFullscreenScreenshot() {
-        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+        mScreenshotHelper.takeScreenshot(
                 WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
     }
 
     @Test
+    public void testFullscreenScreenshotRequest() {
+        ScreenshotRequest request = new ScreenshotRequest.Builder(
+                TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+                .build();
+        mScreenshotHelper.takeScreenshot(request, mHandler, null);
+    }
+
+    @Test
     public void testProvidedImageScreenshot() {
-        mScreenshotHelper.provideScreenshot(
-                new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
-                WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
+        HardwareBuffer buffer = HardwareBuffer.create(
+                10, 10, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+        Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+        ScreenshotRequest request = new ScreenshotRequest.Builder(
+                TAKE_SCREENSHOT_PROVIDED_IMAGE, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+                .setTopComponent(new ComponentName("", ""))
+                .setTaskId(1)
+                .setUserId(1)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(new Rect())
+                .setInsets(Insets.NONE)
+                .build();
+        mScreenshotHelper.takeScreenshot(request, mHandler, null);
     }
 
     @Test
     public void testScreenshotTimesOut() {
         long timeoutMs = 10;
+        ScreenshotRequest request = new ScreenshotRequest.Builder(
+                TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+                .build();
 
         CountDownLatch lock = new CountDownLatch(1);
-        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
-                WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
-                mHandler,
-                timeoutMs,
+        mScreenshotHelper.takeScreenshotInternal(request, mHandler,
                 uri -> {
                     assertNull(uri);
                     lock.countDown();
-                });
+                }, timeoutMs);
 
         try {
             // Add tolerance for delay to prevent flakes.
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
new file mode 100644
index 0000000..89acbc7
--- /dev/null
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ScreenshotRequestTest {
+    private final ComponentName mComponentName =
+            new ComponentName("android.test", "android.test.Component");
+
+    @Test
+    public void testSimpleScreenshot() {
+        ScreenshotRequest in =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build();
+
+        Parcel parcel = Parcel.obtain();
+        in.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+        assertEquals(SCREENSHOT_OTHER, out.getSource());
+        assertNull("Top component was expected to be null", out.getTopComponent());
+        assertEquals(INVALID_TASK_ID, out.getTaskId());
+        assertEquals(USER_NULL, out.getUserId());
+        assertNull("Bitmap was expected to be null", out.getBitmap());
+        assertNull("Bounds were expected to be null", out.getBoundsInScreen());
+        assertEquals(Insets.NONE, out.getInsets());
+    }
+
+    @Test
+    public void testProvidedScreenshot() {
+        Bitmap bitmap = makeHardwareBitmap(50, 50);
+        ScreenshotRequest in =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                        .setTopComponent(mComponentName)
+                        .setTaskId(2)
+                        .setUserId(3)
+                        .setBitmap(bitmap)
+                        .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+                        .setInsets(Insets.of(2, 3, 4, 5))
+                        .build();
+
+        Parcel parcel = Parcel.obtain();
+        in.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(TAKE_SCREENSHOT_PROVIDED_IMAGE, out.getType());
+        assertEquals(SCREENSHOT_OTHER, out.getSource());
+        assertEquals(mComponentName, out.getTopComponent());
+        assertEquals(2, out.getTaskId());
+        assertEquals(3, out.getUserId());
+        assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+        assertEquals(new Rect(10, 10, 60, 60), out.getBoundsInScreen());
+        assertEquals(Insets.of(2, 3, 4, 5), out.getInsets());
+    }
+
+    @Test
+    public void testProvidedScreenshot_nullBitmap() {
+        ScreenshotRequest.Builder inBuilder =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                        .setTopComponent(mComponentName)
+                        .setTaskId(2)
+                        .setUserId(3)
+                        .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+                        .setInsets(Insets.of(2, 3, 4, 5));
+
+        assertThrows(IllegalStateException.class, inBuilder::build);
+    }
+
+    @Test
+    public void testFullScreenshot_withBitmap() {
+        // A bitmap added to a FULLSCREEN request will be ignored, but it's technically valid
+        Bitmap bitmap = makeHardwareBitmap(50, 50);
+        ScreenshotRequest in =
+                new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
+                        .setBitmap(bitmap)
+                        .build();
+
+        Parcel parcel = Parcel.obtain();
+        in.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+        assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+        assertEquals(SCREENSHOT_OTHER, out.getSource());
+        assertNull(out.getTopComponent());
+        assertEquals(INVALID_TASK_ID, out.getTaskId());
+        assertEquals(USER_NULL, out.getUserId());
+        assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+        assertNull("Bounds expected to be null", out.getBoundsInScreen());
+        assertEquals(Insets.NONE, out.getInsets());
+    }
+
+    @Test
+    public void testInvalidType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new ScreenshotRequest.Builder(5, 2).build());
+    }
+
+    private Bitmap makeHardwareBitmap(int width, int height) {
+        HardwareBuffer buffer = HardwareBuffer.create(
+                width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+        return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+    }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 58a2073..caa118a 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -306,6 +306,7 @@
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <!-- Permission required for UiModeManager CTS test -->
         <permission name="android.permission.READ_PROJECTION_STATE"/>
+        <permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
         <permission name="android.permission.READ_WIFI_CREDENTIAL"/>
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f47d9c6..1cf819a 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3331,6 +3331,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "1015746067": {
+      "message": "Display id=%d is ignoring orientation request for %d, return %d following a per-app override for %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "1022095595": {
       "message": "TaskFragment info changed name=%s",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 417a27d..7e2cac1 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -23,7 +23,6 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.animation.ValueAnimator;
-import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -983,9 +982,9 @@
         RippleShader shader = new RippleShader();
         // Grab the color for the current state and cut the alpha channel in
         // half so that the ripple and background together yield full alpha.
-        final int color = clampAlpha(mMaskColorFilter == null
+        final int color = mMaskColorFilter == null
                 ? mState.mColor.getColorForState(getState(), Color.BLACK)
-                : mMaskColorFilter.getColor());
+                : mMaskColorFilter.getColor();
         final int effectColor = mState.mEffectColor.getColorForState(getState(), Color.MAGENTA);
         final float noisePhase = AnimationUtils.currentAnimationTimeMillis();
         shader.setColor(color, effectColor);
@@ -1008,13 +1007,6 @@
         return properties;
     }
 
-    private int clampAlpha(@ColorInt int color) {
-        if (Color.alpha(color) < 128) {
-            return  (color & 0x00FFFFFF) | 0x80000000;
-        }
-        return color;
-    }
-
     @Override
     public void invalidateSelf() {
         invalidateSelf(true);
@@ -1229,7 +1221,7 @@
 
         // Grab the color for the current state and cut the alpha channel in
         // half so that the ripple and background together yield full alpha.
-        final int color = clampAlpha(mState.mColor.getColorForState(getState(), Color.BLACK));
+        final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
         final Paint p = mRipplePaint;
 
         if (mMaskColorFilter != null) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 3adae70..87c2822 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -25,12 +25,12 @@
 import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
+import androidx.window.extensions.core.util.function.Consumer;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 
 /**
  * Reference implementation of androidx.window.extensions.area OEM interface for use with
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index cddbf469..7eee6da 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index f615ad6..0f45219 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -47,6 +47,7 @@
         "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
         "src/com/android/wm/shell/common/TransactionPool.java",
         "src/com/android/wm/shell/animation/Interpolators.java",
+        "src/com/android/wm/shell/pip/PipContentOverlay.java",
         "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
     ],
     path: "src",
diff --git a/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml
new file mode 100644
index 0000000..a3ca74f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/letterbox_restart_button_background_ripple.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral1_900" android:alpha="0.6" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml
new file mode 100644
index 0000000..a3ca74f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/letterbox_restart_dismiss_button_background_ripple.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral1_900" android:alpha="0.6" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
index 416287d..53a8bb1 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
@@ -17,5 +17,4 @@
 <shape android:shape="rectangle"
        xmlns:android="http://schemas.android.com/apk/res/android">
     <solid android:color="@android:color/white" />
-    <corners android:radius="20dp" />
 </shape>
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml
new file mode 100644
index 0000000..60f3cfe
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+       android:shape="rectangle">
+    <solid android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+    <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
new file mode 100644
index 0000000..ef97ea1
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/letterbox_restart_button_background_ripple">
+    <item android:drawable="@drawable/letterbox_restart_button_background"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml
new file mode 100644
index 0000000..c247c6e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_button.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:state_checked="true"
+          android:drawable="@drawable/letterbox_restart_checkbox_checked" />
+    <item android:state_pressed="true"
+          android:drawable="@drawable/letterbox_restart_checkbox_checked" />
+    <item android:state_pressed="false"
+          android:drawable="@drawable/letterbox_restart_checkbox_unchecked" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml
new file mode 100644
index 0000000..4f97e2c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_checked.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="20dp"
+        android:height="20dp"
+        android:viewportWidth="20"
+        android:viewportHeight="20"
+        android:tint="?android:attr/textColorSecondary">
+    <group
+        android:scaleX="0.83333333333"
+        android:scaleY="0.83333333333"
+        android:translateX="0"
+        android:translateY="0">
+        <path
+            android:fillColor="?android:attr/textColorSecondary"
+            android:pathData="M10.6,16.2 L17.65,9.15 16.25,7.75 10.6,13.4 7.75,10.55 6.35,11.95ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21Z"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml
new file mode 100644
index 0000000..bb14d19
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_checkbox_unchecked.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="20dp"
+        android:height="20dp"
+        android:viewportWidth="20"
+        android:viewportHeight="20"
+        android:tint="?android:attr/textColorSecondary">
+    <group
+        android:scaleX="0.83333333333"
+        android:scaleY="0.83333333333"
+        android:translateX="0"
+        android:translateY="0">
+        <path
+            android:fillColor="?android:attr/textColorSecondary"
+            android:pathData="M5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21ZM5,19H19Q19,19 19,19Q19,19 19,19V5Q19,5 19,5Q19,5 19,5H5Q5,5 5,5Q5,5 5,5V19Q5,19 5,19Q5,19 5,19Z"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
new file mode 100644
index 0000000..e3c18a2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dialog_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+       android:shape="rectangle">
+    <solid android:color="?androidprv:attr/colorSurface"/>
+    <corners android:radius="@dimen/letterbox_restart_dialog_corner_radius"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml
new file mode 100644
index 0000000..af89d41
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+       android:shape="rectangle">
+    <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" android:width="1dp"/>
+    <solid android:color="?androidprv:attr/colorSurface" />
+    <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/>
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
new file mode 100644
index 0000000..e32aefc
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/letterbox_restart_dismiss_button_background_ripple">
+    <item android:drawable="@drawable/letterbox_restart_dismiss_button_background"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml
new file mode 100644
index 0000000..5053971
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_header_ic_arrows.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        android:width="@dimen/letterbox_restart_dialog_title_icon_width"
+        android:height="@dimen/letterbox_restart_dialog_title_icon_height"
+        android:viewportWidth="45"
+        android:viewportHeight="44">
+    <group
+        android:scaleX="0.8"
+        android:scaleY="0.8"
+        android:translateX="8"
+        android:translateY="8">
+        <path
+            android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+            android:fillColor="?androidprv:attr/colorAccentPrimaryVariant"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml
new file mode 100644
index 0000000..b6e0172
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_ic_arrows.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/letterbox_restart_dialog_title_icon_width"
+        android:height="@dimen/letterbox_restart_dialog_title_icon_height"
+        android:viewportWidth="45"
+        android:viewportHeight="44">
+    <group
+        android:scaleX="0.8"
+        android:scaleY="0.8"
+        android:translateX="8"
+        android:translateY="8">
+        <path
+            android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+            android:fillColor="@color/compat_controls_text"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
index 2a4cc02..da31a46 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
@@ -17,26 +17,14 @@
 <com.android.wm.shell.windowdecor.WindowDecorLinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/desktop_mode_caption"
-    android:layout_width="wrap_content"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
     android:background="@drawable/desktop_mode_decor_title">
     <Button
-        style="@style/CaptionButtonStyle"
-        android:id="@+id/back_button"
-        android:contentDescription="@string/back_button_text"
-        android:background="@drawable/decor_back_button_dark"
-    />
-    <Button
         android:id="@+id/caption_handle"
         android:layout_width="128dp"
         android:layout_height="32dp"
-        android:layout_margin="5dp"
-        android:padding="4dp"
         android:contentDescription="@string/handle_text"
         android:background="@drawable/decor_handle_dark"/>
-    <Button
-        style="@style/CaptionButtonStyle"
-        android:id="@+id/close_window"
-        android:contentDescription="@string/close_button_text"
-        android:background="@drawable/decor_close_button_dark"/>
 </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
new file mode 100644
index 0000000..ba9852c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
@@ -0,0 +1,142 @@
+<!--
+  ~ 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.
+  -->
+<com.android.wm.shell.compatui.RestartDialogLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@android:color/system_neutral1_900">
+
+    <!-- The background of the top-level layout acts as the background dim. -->
+
+    <!--TODO (b/266288912): Resolve overdraw warning -->
+
+    <!-- Vertical margin will be set dynamically since it depends on task bounds.
+         Setting the alpha of the dialog container to 0, since it shouldn't be visible until the
+         enter animation starts. -->
+    <FrameLayout
+        android:id="@+id/letterbox_restart_dialog_container"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/letterbox_restart_dialog_margin"
+        android:background="@drawable/letterbox_restart_dialog_background"
+        android:alpha="0"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintWidth_max="@dimen/letterbox_restart_dialog_width">
+
+        <!-- The ScrollView should only wrap the content of the dialog, otherwise the background
+             corner radius will be cut off when scrolling to the top/bottom. -->
+
+        <ScrollView android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+
+            <LinearLayout
+                android:padding="24dp"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical">
+
+                <ImageView
+                    android:importantForAccessibility="no"
+                    android:layout_width="@dimen/letterbox_restart_dialog_title_icon_width"
+                    android:layout_height="@dimen/letterbox_restart_dialog_title_icon_height"
+                    android:src="@drawable/letterbox_restart_header_ic_arrows"/>
+
+                <TextView
+                    android:layout_marginVertical="16dp"
+                    android:id="@+id/letterbox_restart_dialog_title"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/letterbox_restart_dialog_title"
+                    android:textAlignment="center"
+                    android:textAppearance="@style/RestartDialogTitleText"/>
+
+                <TextView
+                    android:textAppearance="@style/RestartDialogBodyText"
+                    android:id="@+id/letterbox_restart_dialog_description"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/letterbox_restart_dialog_description"
+                    android:textAlignment="center"/>
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginVertical="32dp">
+
+                    <CheckBox
+                        android:id="@+id/letterbox_restart_dialog_checkbox"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:button="@drawable/letterbox_restart_checkbox_button"/>
+
+                    <TextView
+                        android:textAppearance="@style/RestartDialogCheckboxText"
+                        android:layout_marginStart="12dp"
+                        android:id="@+id/letterbox_restart_dialog_checkbox_description"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:text="@string/letterbox_restart_dialog_checkbox_title"
+                        android:textAlignment="textStart"/>
+
+                </LinearLayout>
+
+                <FrameLayout
+                    android:minHeight="@dimen/letterbox_restart_dialog_button_height"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+
+                    <Button
+                        android:textAppearance="@style/RestartDialogDismissButton"
+                        android:id="@+id/letterbox_restart_dialog_dismiss_button"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:minWidth="@dimen/letterbox_restart_dialog_button_width"
+                        android:minHeight="@dimen/letterbox_restart_dialog_button_height"
+                        android:layout_gravity="start"
+                        android:background=
+                            "@drawable/letterbox_restart_dismiss_button_background_ripple"
+                        android:text="@string/letterbox_restart_cancel"
+                        android:contentDescription="@string/letterbox_restart_cancel"/>
+
+                    <Button
+                        android:textAppearance="@style/RestartDialogConfirmButton"
+                        android:id="@+id/letterbox_restart_dialog_restart_button"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:minWidth="@dimen/letterbox_restart_dialog_button_width"
+                        android:minHeight="@dimen/letterbox_restart_dialog_button_height"
+                        android:layout_gravity="end"
+                        android:background=
+                            "@drawable/letterbox_restart_button_background_ripple"
+                        android:text="@string/letterbox_restart_restart"
+                        android:contentDescription="@string/letterbox_restart_restart"/>
+
+                </FrameLayout>
+
+            </LinearLayout>
+
+        </ScrollView>
+
+    </FrameLayout>
+
+</com.android.wm.shell.compatui.RestartDialogLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 3d50d22..33e6aa6 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik buite ’n program om dit te herposisioneer"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Het dit"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vou uit vir meer inligting."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Herbegin vir ’n beter aansig?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Jy kan die app herbegin sodat dit beter op jou skerm lyk, maar jy sal dalk jou vordering of enige ongestoorde veranderinge verloor"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselleer"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Herbegin"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Moenie weer wys nie"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeer"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Maak klein"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Maak toe"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 70304aa..b6b99e5 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 0f74aab..7b2aded 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"انقر مرّتين خارج تطبيق لتغيير موضعه."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string>
     <string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index a0213f4..11a7e32 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"এপ্‌টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string>
     <string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index f842bfe..10663ee 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tətbiqin yerini dəyişmək üçün kənarına iki dəfə toxunun"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ətraflı məlumat üçün genişləndirin."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Daha yaxşı görünüş üçün yenidən başladılsın?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Tətbiqi yenidən başlada bilərsiniz ki, ekranınızda daha yaxşı görünsün, lakin irəliləyişi və ya yadda saxlanmamış dəyişiklikləri itirə bilərsiniz"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ləğv edin"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yenidən başladın"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Yenidən göstərməyin"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Böyüdün"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kiçildin"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 540ae7c..47e661c5 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste promenili njenu poziciju"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Važi"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za još informacija."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li da restartujete radi boljeg prikaza?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete da restartujete aplikaciju da bi izgledala bolje na ekranu, s tim što možete da izgubite ono što ste uradili ili nesačuvane promene, ako ih ima"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartuj"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Uvećajte"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Umanjite"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index bea7538..0961f30 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двойчы націсніце экран па-за праграмай, каб перамясціць яе"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 59915e6..6a0648b 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Докоснете два пъти извън дадено приложение, за да промените позицията му"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 63c9684..5e80104 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string>
     <string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index b725efe..2405b2a 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da promijenite njen položaj"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Razumijem"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite za više informacija."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Ponovo pokrenuti za bolji prikaz?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete ponovo pokrenuti aplikaciju da bolje izgleda na ekranu, ali možete izgubiti napredak ili izmjene koje nisu sačuvane"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Otkaži"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Ponovo pokreni"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovo"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziranje"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiziranje"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 4383916..3b070d8 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Fes doble toc fora d\'una aplicació per canviar-ne la posició"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index e5cb26f..3606eaa 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikaci změníte její umístění"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovat"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 46f7c69..487d412 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryk to gange uden for en app for at justere dens placering"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 1269d36..0ec59e7 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index f8a69ef..4f4265a 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Πατήστε δύο φορές έξω από μια εφαρμογή για να αλλάξετε τη θέση της"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Το κατάλαβα"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ανάπτυξη για περισσότερες πληροφορίες."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Επανεκκίνηση για καλύτερη προβολή;"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Μπορείτε να επανεκκινήσετε την εφαρμογή για να προβάλλεται καλύτερα στην οθόνη σας, αλλά η πρόοδός σας και τυχόν μη αποθηκευμένες αλλαγές ενδέχεται να χαθούν."</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ακύρωση"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Επανεκκίνηση"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Να μην εμφανιστεί ξανά"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Μεγιστοποίηση"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Ελαχιστοποίηση"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index 8e46c3e..e29b01b 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 7cbbf64..9228c59 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so it looks better on your screen, but you may lose your progress or any unsaved changes"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don’t show again"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index 8e46c3e..e29b01b 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index 8e46c3e..e29b01b 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restart for a better view?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"You can restart the app so that it looks better on your screen, but you may lose your progress or any unsaved changes"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restart"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Don\'t show again"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Close"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index b2720be..cf23114 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‏‎Double-tap outside an app to reposition it‎‏‎‎‏‎"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‎Got it‎‏‎‎‏‎"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎Expand for more information.‎‏‎‎‏‎"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‎‎‏‎Restart for a better view?‎‏‎‎‏‎"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎You can restart the app so it looks better on your screen, but you may lose your progress or any unsaved changes‎‏‎‎‏‎"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎Cancel‎‏‎‎‏‎"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‏‎Restart‎‏‎‎‏‎"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎Don’t show again‎‏‎‎‏‎"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎Maximize‎‏‎‎‏‎"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‏‎‏‏‎‎‏‎Minimize‎‏‎‎‏‎"</string>
     <string name="close_button_text" msgid="2913281996024033299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎Close‎‏‎‎‏‎"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 47445a7..a147be3 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Presiona dos veces fuera de una app para cambiar su ubicación"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expande para obtener más información."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"¿Quieres reiniciar para que se vea mejor?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puedes reiniciar la app para que se vea mejor en la pantalla, pero podrías perder tu progreso o cualquier cambio que no hayas guardado"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 6c45231..9c5aa92 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dos veces fuera de una aplicación para cambiarla de posición"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index a8dc08c..f7eafbd 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index 9fbf0a0..a46095a 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Aplikazioaren posizioa aldatzeko, sakatu birritan haren kanpoaldea"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ados"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Informazio gehiago lortzeko, zabaldu hau."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Aplikazioa berrabiarazi nahi duzu itxura hobea izan dezan?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Aplikazioa berrabiarazi egin dezakezu itxura hobea izan dezan, baina agian garapena edo gorde gabeko aldaketak galduko dituzu"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Utzi"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Berrabiarazi"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ez erakutsi berriro"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizatu"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizatu"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index e7cb5f4..ea8fafe 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابه‌جا کردن برنامه، بیرون از آن دوضربه بزنید"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجه‌ام"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
     <string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 86199f3..298de64 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 1f3ac9e..36b40bd 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index f1dbb35..7f3f914 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Appuyez deux fois en dehors d\'une appli pour la repositionner"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 6e215a1..3d5265f 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dúas veces fóra da aplicación para cambiala de posición"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index ad086bb..d2e5a82 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"કોઈ ઍપની જગ્યા બદલવા માટે, તેની બહાર બે વાર ટૅપ કરો"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"સમજાઈ ગયું"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"વધુ માહિતી માટે મોટું કરો."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"બહેતર વ્યૂ માટે ફરીથી શરૂ કરીએ?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"તમે ઍપને ફરીથી શરૂ કરી શકો છો, જેથી તે તમારી સ્ક્રીન પર વધુ સારી રીતે દેખાય, પરંતુ આમ કરવાથી તમે તમારી ઍપ પર કરી હોય એવી કોઈ પ્રક્રિયાની પ્રગતિ અથવા સાચવ્યા ન હોય એવો કોઈપણ ફેરફાર ગુમાવી શકો છો"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"રદ કરો"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"ફરી શરૂ કરો"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ફરીથી બતાવશો નહીં"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"મોટું કરો"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"નાનું કરો"</string>
     <string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index bed39fb..b217978 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 1446e70..bbd95f7 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvaput dodirnite izvan aplikacije da biste je premjestili"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Shvaćam"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Proširite da biste saznali više."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite li ponovno pokrenuti za bolji pregled?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Možete ponovno pokrenuti aplikaciju tako da bolje izgleda na zaslonu, no mogli biste izgubiti napredak ili sve nespremljene promjene"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Odustani"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Pokreni ponovno"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikazuj ponovno"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiziraj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiziraj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zatvori"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 221c329..1bb2213 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Koppintson duplán az alkalmazáson kívül az áthelyezéséhez"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Értem"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kibontással további információkhoz juthat."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Újraindítja a jobb megjelenítés érdekében?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Újraindíthatja az alkalmazást a képernyőn való jobb megjelenítés érdekében, de elveszítheti az előrehaladását és az esetleges nem mentett változásokat"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Mégse"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Újraindítás"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne jelenjen meg többé"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Teljes méret"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kis méret"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 7be9941..7d556b2 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 4e760ef..b51dc00 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 50d4ee7..d924c47 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ýttu tvisvar utan við forrit til að færa það"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index d2595f7..98d1b45 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tocca due volte fuori da un\'app per riposizionarla"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Espandi per avere ulteriori informazioni."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vuoi riavviare per migliorare la visualizzazione?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puoi riavviare l\'app affinché venga visualizzata meglio sullo schermo, ma potresti perdere i tuoi progressi o eventuali modifiche non salvate"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annulla"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Riavvia"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrare più"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Ingrandisci"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Riduci a icona"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 883596e..d116257 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
     <string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 6bb22a2..548d63c 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"位置を変えるにはアプリの外側をダブルタップしてください"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"開くと詳細が表示されます。"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"再起動して画面をすっきりさせますか?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"アプリを再起動して画面をすっきりさせることはできますが、進捗状況が失われ、保存されていない変更が消える可能性があります"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"キャンセル"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"再起動"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"次回から表示しない"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 6cf7d78..1b359b39e6 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ორმაგად შეეხეთ აპის გარშემო სივრცეს, რათა ის სხვაგან გადაიტანოთ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"გასაგებია"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"დამატებითი ინფორმაციისთვის გააფართოეთ."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"გსურთ გადატვირთვა უკეთესი ხედისთვის?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"შეგიძლიათ გადატვირთოთ აპი იმისათვის, რომ თქვენს ეკრანზე უკეთესად გამოჩნდეს, თუმცა თქვენ მიერ შესრულებული მოქმედებები შეიძლება დაიკარგოს ან ცვლილებების შენახვა ვერ მოხერხდეს"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"გაუქმება"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"გადატვირთვა"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"აღარ გამოჩნდეს"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"მაქსიმალურად გაშლა"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ჩაკეცვა"</string>
     <string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 216619a..02560ca 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 79aca62..32ca8e3 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ចុចពីរដង​នៅ​ក្រៅ​កម្មវិធី ដើម្បី​ប្ដូរ​ទីតាំង​កម្មវិធី​នោះ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string>
     <string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 9e9333e..9b47a00 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ಆ್ಯಪ್ ಒಂದರ ಸ್ಥಾನವನ್ನು ಬದಲಾಯಿಸಲು ಅದರ ಹೊರಗೆ ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ಸರಿ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ಇನ್ನಷ್ಟು ಮಾಹಿತಿಗಾಗಿ ವಿಸ್ತೃತಗೊಳಿಸಿ."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ಉತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬೇಕೆ?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಬಹುದು, ಇದರಿಂದ ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ನಲ್ಲಿ ಆ್ಯಪ್ ಉತ್ತಮವಾಗಿ ಕಾಣಿಸುತ್ತದೆ, ಆದರೆ ನಿಮ್ಮ ಪ್ರಗತಿಯನ್ನು ಅಥವಾ ಯಾವುದೇ ಉಳಿಸದ ಬದಲಾವಣೆಗಳನ್ನು ನೀವು ಕಳೆದುಕೊಳ್ಳಬಹುದು"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ರದ್ದುಮಾಡಿ"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"ಮರುಪ್ರಾರಂಭಿಸಿ"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ಮತ್ತೊಮ್ಮೆ ತೋರಿಸಬೇಡಿ"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ಹಿಗ್ಗಿಸಿ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ಕುಗ್ಗಿಸಿ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index f9b495a..844a9fa 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"최소화"</string>
     <string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 7d39c45..c2368d2 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -19,7 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="pip_phone_close" msgid="5783752637260411309">"Жабуу"</string>
     <string name="pip_phone_expand" msgid="2579292903468287504">"Жайып көрсөтүү"</string>
-    <string name="pip_phone_settings" msgid="5468987116750491918">"Жөндөөлөр"</string>
+    <string name="pip_phone_settings" msgid="5468987116750491918">"Параметрлер"</string>
     <string name="pip_phone_enter_split" msgid="7042877263880641911">"Экранды бөлүү режимине өтүү"</string>
     <string name="pip_menu_title" msgid="5393619322111827096">"Меню"</string>
     <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> – сүрөт ичиндеги сүрөт"</string>
@@ -51,7 +51,7 @@
     <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чыгуу үчүн экранды ылдый жагынан өйдө сүрүңүз же колдонмонун өйдө жагын басыңыз"</string>
     <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бир кол режимин баштоо"</string>
     <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Бир кол режиминен чыгуу"</string>
-    <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> калкып чыкма билдирмелер жөндөөлөрү"</string>
+    <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g> калкып чыкма билдирмелер параметрлери"</string>
     <string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"Кошумча меню"</string>
     <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"Кайра топтомго кошуу"</string>
     <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="APP_NAME">%2$s</xliff:g> колдонмосунан <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
@@ -60,7 +60,7 @@
     <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Жогорку оң жакка жылдыруу"</string>
     <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Төмөнкү сол жакка жылдыруу"</string>
     <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Төмөнкү оң жакка жылдыруу"</string>
-    <string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> жөндөөлөрү"</string>
+    <string name="bubbles_app_settings" msgid="3617224938701566416">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> параметрлери"</string>
     <string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string>
     <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Жазышууда калкып чыкма билдирмелер көрүнбөсүн"</string>
     <string name="bubbles_user_education_title" msgid="2112319053732691899">"Калкып чыкма билдирмелер аркылуу маектешүү"</string>
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Колдонмону жылдыруу үчүн сырт жагын эки жолу таптаңыз"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Түшүндүм"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толук маалымат алуу үчүн жайып көрүңүз."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Жакшыраак көрүү үчүн өчүрүп күйгүзөсүзбү?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Экранда жакшыраак көрүү үчүн колдонмону өчүрүп күйгүзө аласыз, бирок аткарылган иш же сакталбаган өзгөрүүлөр өчүрүлүшү мүмкүн"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Токтотуу"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өчүрүп күйгүзүү"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Экинчи көрүнбөсүн"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Чоңойтуу"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Кичирейтүү"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 8e42aa3..f4ff5e5 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ແຕະສອງເທື່ອໃສ່ນອກແອັບໃດໜຶ່ງເພື່ອຈັດຕຳແໜ່ງຂອງມັນຄືນໃໝ່"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ເຂົ້າໃຈແລ້ວ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ຂະຫຍາຍເພື່ອເບິ່ງຂໍ້ມູນເພີ່ມເຕີມ."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ຣີສະຕາດເພື່ອໃຫ້ມີມຸມມອງທີ່ດີຂຶ້ນບໍ?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ທ່ານສາມາດຣີສະຕາດແອັບໄດ້ເພື່ອໃຫ້ມັນເບິ່ງດີຂຶ້ນໃນໜ້າຈໍຂອງທ່ານ ແຕ່ທ່ານອາດຈະສູນເສຍຄວາມຄືບໜ້າ ຫຼື ການປ່ຽນແປງທີ່ບໍ່ໄດ້ບັນທຶກໄວ້"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ຍົກເລີກ"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"ຣີສະຕາດ"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ບໍ່ຕ້ອງສະແດງອີກ"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ຫຍໍ້ລົງ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ປິດ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index dc99690..0d276ec 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dukart palieskite už programos ribų, kad pakeistumėte jos poziciją"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Supratau"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Išskleiskite, jei reikia daugiau informacijos."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Paleisti iš naujo, kad būtų geresnis vaizdas?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Galite iš naujo paleisti programą, kad ji geriau atrodytų ekrane, bet galite prarasti eigą ir neišsaugotus pakeitimus"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atšaukti"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Paleisti iš naujo"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Daugiau neberodyti"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Padidinti"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Sumažinti"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index bd2eef4..c6ccc27 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Lai pārvietotu lietotni, veiciet dubultskārienu ārpus lietotnes"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Labi"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Izvērsiet, lai iegūtu plašāku informāciju."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vai restartēt, lai uzlabotu skatu?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Varat restartēt lietotni, lai tā labāk izskatītos ekrānā, taču, iespējams, zaudēsiet paveikto vai nesaglabātas izmaiņas (ja tādas ir)."</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Atcelt"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartēt"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vairs nerādīt"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizēt"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizēt"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index d133654..526093d 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Допрете двапати надвор од некоја апликација за да ја преместите"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Сфатив"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширете за повеќе информации."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Да се рестартира за подобар приказ?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Може да ја рестартирате апликацијата за да изгледа подобро на екранот, но може да го изгубите напредокот или незачуваните промени"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартирај"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не прикажувај повторно"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Зголеми"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Минимизирај"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затвори"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 16927bf..bd3d94e 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ആപ്പിന്റെ സ്ഥാനം മാറ്റാൻ അതിന് പുറത്ത് ഡബിൾ ടാപ്പ് ചെയ്യുക"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"മനസ്സിലായി"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"കൂടുതൽ വിവരങ്ങൾക്ക് വികസിപ്പിക്കുക."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"മെച്ചപ്പെട്ട കാഴ്‌ചയ്‌ക്കായി റീസ്റ്റാർട്ട് ചെയ്യണോ?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ആപ്പ് റീസ്റ്റാർട്ട് ചെയ്യുകയാണെങ്കിൽ ഇത് നിങ്ങളുടെ സ്‌ക്രീനിൽ മെച്ചപ്പെട്ടതായി കാണും, എന്നാൽ ഇതുവരെയുള്ള പുരോഗതിയും സംരക്ഷിക്കാത്ത മാറ്റങ്ങളും നിങ്ങൾക്ക് നഷ്‌ടമാകും"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"റദ്ദാക്കുക"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"റീസ്റ്റാർട്ട് ചെയ്യൂ"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"വീണ്ടും കാണിക്കരുത്"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"വലുതാക്കുക"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ചെറുതാക്കുക"</string>
     <string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 264f9a0..6c1c0b5 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 7a475ed..01bf949 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ॲपची स्थिती पुन्हा बदलण्यासाठी, त्याच्या बाहेर दोनदा टॅप करा"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"समजले"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"अधिक माहितीसाठी विस्तार करा."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"आणखी चांगल्या प्रकारे दिसावे यासाठी रीस्टार्ट करायचे आहे का?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"तुम्ही अ‍ॅप रीस्टार्ट करू शकता, जेणेकरून ते तुमच्या स्क्रीनवर आणखी चांगल्या प्रकारे दिसेल, पण तुमची प्रगती किंवा कोणतेही सेव्ह न केलेले बदल तुम्ही गमवाल"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करा"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करा"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"पुन्हा दाखवू नका"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"मोठे करा"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"लहान करा"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index be1dc24..80efab8 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketik dua kali di luar apl untuk menempatkan semula apl itu"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimumkan"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 7b2b7c5..be0815e 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"နားလည်ပြီ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 3e18b49..8d15086 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dobbelttrykk utenfor en app for å flytte den"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 4b10f5a..f1997be 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"तपाईं जुन एपको स्थिति मिलाउन चाहनुहुन्छ सोही एपको बाहिर डबल ट्याप गर्नुहोस्"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"बुझेँ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"थप जानकारी प्राप्त गर्न चाहनुहुन्छ भने एक्स्पान्ड गर्नुहोस्।"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"अझ राम्रोसँग देखिने बनाउन एप रिस्टार्ट गर्ने हो?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"यो एप तपाईंको स्क्रिनमा अझ राम्रोसँग देखियोस् भन्नाका लागि तपाईं सो एप रिस्टार्ट गर्न सक्नुहुन्छ तर तपाईंले अहिलेसम्म गरेका क्रियाकलाप वा सेभ गर्न बाँकी परिवर्तनहरू हट्न सक्छन्"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द गर्नुहोस्"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"रिस्टार्ट गर्नुहोस्"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फेरि नदेखाइयोस्"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ठुलो बनाउनुहोस्"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"मिनिमाइज गर्नुहोस्"</string>
     <string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index b056483..ba37ca7 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dubbeltik naast een app om deze opnieuw te positioneren"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Uitvouwen voor meer informatie."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Opnieuw opstarten voor een betere weergave?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Je kunt de app opnieuw opstarten zodat deze er beter uitziet op je scherm, maar je kunt je voortgang of niet-opgeslagen wijzigingen kwijtraken"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuleren"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Opnieuw opstarten"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Niet opnieuw tonen"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximaliseren"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimaliseren"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 5fd81f4..0a8882d 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index ada79d1..62f15bf 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index a97fd5c..d82f60f6 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kliknij dwukrotnie poza aplikacją, aby ją przenieść"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 8edcddf..a94d157 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Reiniciar para melhorar a visualização?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Você pode reiniciar o app para melhorar a visualização dele, mas talvez perca seu progresso ou mudanças não salvas"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index e0636d4..7435faf 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de uma app para a reposicionar"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 8edcddf..a94d157 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de um app para reposicionar"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendi"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Abra para ver mais informações."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Reiniciar para melhorar a visualização?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Você pode reiniciar o app para melhorar a visualização dele, mas talvez perca seu progresso ou mudanças não salvas"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar novamente"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 9227216..fa9a074 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Atinge de două ori lângă o aplicație pentru a o repoziționa"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Extinde pentru mai multe informații"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Repornești pentru o vizualizare mai bună?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Poți să repornești aplicația ca să arate mai bine pe ecran, dar este posibil să pierzi progresul sau modificările nesalvate"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulează"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Repornește"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nu mai afișa"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximizează"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizează"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index f5fa1a4..b9c5951 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Чтобы переместить приложение, дважды нажмите рядом с ним."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОК"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Развернуть, чтобы узнать больше."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Перезапустить приложение?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Вы можете перезапустить приложение, чтобы оно лучше смотрелось на экране. При этом ваш прогресс или несохраненные изменения могут быть утеряны."</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Отмена"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустить"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больше не показывать"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Развернуть"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Свернуть"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index 53e52f2..e408655 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string>
     <string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index f004fd4..7bb415d 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikácie zmeníte jej pozíciu"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Dobre"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Po rozbalení sa dozviete viac."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Chcete ju reštartovať, aby mala lepší vzhľad?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Aplikáciu môžete reštartovať, aby mala na obrazovke lepší vzhľad, ale môžete prísť o postup a všetky neuložené zmeny."</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušiť"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reštartovať"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Už nezobrazovať"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovať"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimalizovať"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zavrieť"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index c4808059..ac9c2be 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti."</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index b59d4db..f4fc5e7 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Trokit dy herë jashtë një aplikacioni për ta ripozicionuar"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"E kuptova"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Zgjeroje për më shumë informacion."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Rinis për një pamje më të mirë?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Mund të rinisësh aplikacionin në mënyrë që të duket më mirë në ekranin tënd, por mund të humbësh progresin ose çdo ndryshim të paruajtur"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anulo"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Rinis"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Mos e shfaq përsëri"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Maksimizo"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimizo"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 78d74d74..743f9a8 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двапут додирните изван апликације да бисте променили њену позицију"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Важи"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Проширите за још информација."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Желите ли да рестартујете ради бољег приказа?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартујете апликацију да би изгледала боље на екрану, с тим што можете да изгубите оно што сте урадили или несачуване промене, ако их има"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Откажи"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартуј"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Не приказуј поново"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Увећајте"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Умањите"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index cda3040..ba57851 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryck snabbt två gånger utanför en app för att flytta den"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index fee34eb..43b415c 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Gusa mara mbili nje ya programu ili uihamishe"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 49f128d..3e65964 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string>
     <string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index f0c8be5..7d09fc4 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string>
     <string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 2437e03..a9100e8 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"แตะสองครั้งด้านนอกแอปเพื่อเปลี่ยนตำแหน่ง"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"รับทราบ"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ขยายเพื่อดูข้อมูลเพิ่มเติม"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"รีสตาร์ทเพื่อรับมุมมองที่ดียิ่งขึ้นใช่ไหม"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"คุณรีสตาร์ทแอปเพื่อรับมุมมองที่ดียิ่งขึ้นบนหน้าจอได้ แต่ความคืบหน้าและการเปลี่ยนแปลงใดๆ ที่ไม่ได้บันทึกอาจหายไป"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ยกเลิก"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"รีสตาร์ท"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ไม่ต้องแสดงข้อความนี้อีก"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"ขยายใหญ่สุด"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"ย่อ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"ปิด"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 86ef757..7a4232c 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Mag-double tap sa labas ng app para baguhin ang posisyon nito"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"I-expand para sa higit pang impormasyon."</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"I-restart para sa mas magandang hitsura?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puwede mong i-restart ang app para maging mas maganda ang itsura nito sa iyong screen, pero posibleng mawala ang pag-usad mo o anumang hindi na-save na pagbabago"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Kanselahin"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"I-restart"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Huwag nang ipakita ulit"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"I-maximize"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"I-minimize"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Isara"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index c4060cc..015cfc8 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 166041d..1ed9069 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index ca6a937..8f818cb 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"کسی ایپ کی پوزیشن تبدیل کرنے کے لیے اس ایپ کے باہر دو بار تھپتھپائیں"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"سمجھ آ گئی"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"مزید معلومات کے لیے پھیلائیں۔"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"بہتر منظر کے لیے ری سٹارٹ کریں؟"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"آپ ایپ کو ری سٹارٹ کر سکتے ہیں تاکہ یہ آپ کی اسکرین پر بہتر نظر آئے، تاہم آپ اپنی پیشرفت سے یا کسی غیر محفوظ شدہ تبدیلیوں سے محروم ہو سکتے ہیں"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"منسوخ کریں"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"ری اسٹارٹ کریں"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوبارہ نہ دکھائیں"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"بڑا کریں"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"چھوٹا کریں"</string>
     <string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 8f173d5..0bbdf4f 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Qayta joylash uchun ilova tashqarisiga ikki marta bosing"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 1d5b9d6..d8e1318 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 87f2973..88d6aa6 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -82,6 +82,16 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在某个应用外连续点按两次,即可调整它的位置"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</string>
+    <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
+    <skip />
+    <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
+    <skip />
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index f9b22d22..6bd05c6 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕按兩下即可調整位置"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"你可以重新啟動應用程式,讓系統更新檢視畫面。不過,系統可能不會儲存目前進度及你所做的任何變更"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 1438e52..082049f 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在應用程式外輕觸兩下即可調整位置"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"我知道了"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳細資訊。"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"你可以重新啟動應用程式,讓系統更新檢視畫面。不過,系統可能不會儲存目前進度及你所做的任何變更"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
     <string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index e9238dc..18f722c 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -82,6 +82,11 @@
     <string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Thepha kabili ngaphandle kwe-app ukuze uyimise kabusha"</string>
     <string name="letterbox_education_got_it" msgid="4057634570866051177">"Ngiyezwa"</string>
     <string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Nweba ukuze uthole ulwazi olwengeziwe"</string>
+    <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Qala kabusha ukuze uthole ukubuka okungcono?"</string>
+    <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Ungakwazi ukuqala kabusha i-app ukuze ibukeke kangcono esikrinini sakho, kodwa ungase ulahlekelwe ukuqhubeka kwakho nanoma yiziphi izinguquko ezingalondoloziwe"</string>
+    <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Khansela"</string>
+    <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qala kabusha"</string>
+    <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ungabonisi futhi"</string>
     <string name="maximize_button_text" msgid="1650859196290301963">"Khulisa"</string>
     <string name="minimize_button_text" msgid="271592547935841753">"Nciphisa"</string>
     <string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 774f6c6..76eb094 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -105,6 +105,10 @@
         1.777778
     </item>
 
+    <!-- The aspect ratio that by which optimizations to large screen sizes are made.
+         Needs to be less that or equal to 1. -->
+    <item name="config_pipLargeScreenOptimizedAspectRatio" format="float" type="dimen">0.5625</item>
+
     <!-- The default gravity for the picture-in-picture window.
          Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
     <integer name="config_defaultPictureInPictureGravity">0x55</integer>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 3ee20ea..8908959 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -273,6 +273,39 @@
     <!-- The space between two actions in the letterbox education dialog -->
     <dimen name="letterbox_education_dialog_space_between_actions">24dp</dimen>
 
+    <!-- The margin between the dialog container and its parent. -->
+    <dimen name="letterbox_restart_dialog_margin">24dp</dimen>
+
+    <!-- The corner radius of the restart confirmation dialog. -->
+    <dimen name="letterbox_restart_dialog_corner_radius">28dp</dimen>
+
+    <!-- The fixed width of the dialog if there is enough space in the parent. -->
+    <dimen name="letterbox_restart_dialog_width">348dp</dimen>
+
+    <!-- The width of the top icon in the restart confirmation dialog. -->
+    <dimen name="letterbox_restart_dialog_title_icon_width">32dp</dimen>
+
+    <!-- The height of the top icon in the restart confirmation dialog. -->
+    <dimen name="letterbox_restart_dialog_title_icon_height">32dp</dimen>
+
+    <!-- The width of an icon in the restart confirmation dialog. -->
+    <dimen name="letterbox_restart_dialog_icon_width">40dp</dimen>
+
+    <!-- The height of an icon in the restart confirmation dialog. -->
+    <dimen name="letterbox_restart_dialog_icon_height">32dp</dimen>
+
+    <!-- The space between two actions in the restart confirmation dialog -->
+    <dimen name="letterbox_restart_dialog_space_between_actions">24dp</dimen>
+
+    <!-- The width of the buttons in the restart dialog -->
+    <dimen name="letterbox_restart_dialog_button_width">82dp</dimen>
+
+    <!-- The width of the buttons in the restart dialog -->
+    <dimen name="letterbox_restart_dialog_button_height">36dp</dimen>
+
+    <!-- The corner radius of the buttons in the restart dialog -->
+    <dimen name="letterbox_restart_dialog_button_radius">18dp</dimen>
+
     <!-- The width of the brand image on staring surface. -->
     <dimen name="starting_surface_brand_image_width">200dp</dimen>
 
@@ -331,8 +364,8 @@
     <!-- Height of button (32dp)  + 2 * margin (5dp each). -->
     <dimen name="freeform_decor_caption_height">42dp</dimen>
 
-    <!-- Width of buttons (64dp) + handle (128dp) + padding (24dp total). -->
-    <dimen name="freeform_decor_caption_width">216dp</dimen>
+    <!-- Width of buttons (32dp each) + padding (128dp total). -->
+    <dimen name="freeform_decor_caption_menu_width">256dp</dimen>
 
     <dimen name="freeform_resize_handle">30dp</dimen>
 
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 25eddf8..250dac6 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -188,6 +188,23 @@
     <!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] -->
     <string name="letterbox_education_expand_button_description">Expand for more information.</string>
 
+    <!-- The title of the restart confirmation dialog. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_restart_dialog_title">Restart for a better view?</string>
+
+    <!-- The description of the restart confirmation dialog. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_restart_dialog_description">You can restart the app so it looks better on
+        your screen, but you may lose your progress or any unsaved changes
+    </string>
+
+    <!-- Button text for dismissing the restart confirmation dialog. [CHAR LIMIT=20] -->
+    <string name="letterbox_restart_cancel">Cancel</string>
+
+    <!-- Button text for dismissing the restart confirmation dialog. [CHAR LIMIT=20] -->
+    <string name="letterbox_restart_restart">Restart</string>
+
+    <!-- Checkbox text for asking to not show the restart confirmation dialog again. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_restart_dialog_checkbox_title">Don\u2019t show again</string>
+
     <!-- Freeform window caption strings -->
     <!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
     <string name="maximize_button_text">Maximize</string>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index a859721..e8f340c 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -63,4 +63,42 @@
         <item name="android:lineHeight">16sp</item>
         <item name="android:textColor">@color/tv_pip_edu_text</item>
     </style>
+
+    <style name="RestartDialogTitleText">
+        <item name="android:textSize">24sp</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:lineSpacingExtra">2sp</item>
+        <item name="android:textAppearance">
+            @*android:style/TextAppearance.DeviceDefault.Headline
+        </item>
+    </style>
+
+    <style name="RestartDialogBodyText">
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.02</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:lineSpacingExtra">2sp</item>
+        <item name="android:textAppearance">
+            @*android:style/TextAppearance.DeviceDefault.Body2
+        </item>
+    </style>
+
+    <style name="RestartDialogCheckboxText">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:lineSpacingExtra">4sp</item>
+        <item name="android:textAppearance">@*android:style/TextAppearance.DeviceDefault</item>
+    </style>
+
+    <style name="RestartDialogDismissButton">
+        <item name="android:lineSpacingExtra">2sp</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="RestartDialogConfirmButton">
+        <item name="android:lineSpacingExtra">2sp</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+    </style>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 2363092..aaeef19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -82,7 +82,7 @@
     /** Flag for U animation features */
     public static boolean IS_U_ANIMATION_ENABLED =
             SystemProperties.getInt("persist.wm.debug.predictive_back_anim",
-                    SETTING_VALUE_OFF) == SETTING_VALUE_ON;
+                    SETTING_VALUE_ON) == SETTING_VALUE_ON;
     /** Predictive back animation developer option */
     private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
     // TODO (b/241808055) Find a appropriate time to remove during refactor
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 09dc68a..e24c228 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -21,6 +21,7 @@
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
 import android.annotation.DimenRes;
+import android.annotation.Hide;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
@@ -125,7 +126,7 @@
     private Icon mIcon;
     private boolean mIsBubble;
     private boolean mIsTextChanged;
-    private boolean mIsClearable;
+    private boolean mIsDismissable;
     private boolean mShouldSuppressNotificationDot;
     private boolean mShouldSuppressNotificationList;
     private boolean mShouldSuppressPeek;
@@ -180,7 +181,7 @@
     @VisibleForTesting(visibility = PRIVATE)
     public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
             final int desiredHeight, final int desiredHeightResId, @Nullable final String title,
-            int taskId, @Nullable final String locus, Executor mainExecutor,
+            int taskId, @Nullable final String locus, boolean isDismissable, Executor mainExecutor,
             final Bubbles.BubbleMetadataFlagListener listener) {
         Objects.requireNonNull(key);
         Objects.requireNonNull(shortcutInfo);
@@ -189,6 +190,7 @@
         mKey = key;
         mGroupKey = null;
         mLocusId = locus != null ? new LocusId(locus) : null;
+        mIsDismissable = isDismissable;
         mFlags = 0;
         mUser = shortcutInfo.getUserHandle();
         mPackageName = shortcutInfo.getPackage();
@@ -245,6 +247,11 @@
         return mKey;
     }
 
+    @Hide
+    public boolean isDismissable() {
+        return mIsDismissable;
+    }
+
     /**
      * @see StatusBarNotification#getGroupKey()
      * @return the group key for this bubble, if one exists.
@@ -526,7 +533,7 @@
             mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
         }
 
-        mIsClearable = entry.isClearable();
+        mIsDismissable = entry.isDismissable();
         mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
         mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
         mShouldSuppressPeek = entry.shouldSuppressPeek();
@@ -605,7 +612,7 @@
      * Whether this notification should be shown in the shade.
      */
     boolean showInShade() {
-        return !shouldSuppressNotification() || !mIsClearable;
+        return !shouldSuppressNotification() || !mIsDismissable;
     }
 
     /**
@@ -870,7 +877,7 @@
         pw.print("  desiredHeight: "); pw.println(getDesiredHeightString());
         pw.print("  suppressNotif: "); pw.println(shouldSuppressNotification());
         pw.print("  autoExpand:    "); pw.println(shouldAutoExpand());
-        pw.print("  isClearable:   "); pw.println(mIsClearable);
+        pw.print("  isDismissable: "); pw.println(mIsDismissable);
         pw.println("  bubbleMetadataFlagListener null: " + (mBubbleMetadataFlagListener == null));
         if (mExpandedView != null) {
             mExpandedView.dump(pw);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index 3a59614..e37c785 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -109,7 +109,8 @@
                     b.rawDesiredHeightResId,
                     b.title,
                     b.taskId,
-                    b.locusId?.id
+                    b.locusId?.id,
+                    b.isDismissable
             )
         }
     }
@@ -205,6 +206,7 @@
                                 entity.title,
                                 entity.taskId,
                                 entity.locus,
+                                entity.isDismissable,
                                 mainExecutor,
                                 bubbleMetadataFlagListener
                         )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
index 5f42826..afe19c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
@@ -38,18 +38,18 @@
     private StatusBarNotification mSbn;
     private Ranking mRanking;
 
-    private boolean mIsClearable;
+    private boolean mIsDismissable;
     private boolean mShouldSuppressNotificationDot;
     private boolean mShouldSuppressNotificationList;
     private boolean mShouldSuppressPeek;
 
     public BubbleEntry(@NonNull StatusBarNotification sbn,
-            Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot,
+            Ranking ranking, boolean isDismissable, boolean shouldSuppressNotificationDot,
             boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) {
         mSbn = sbn;
         mRanking = ranking;
 
-        mIsClearable = isClearable;
+        mIsDismissable = isDismissable;
         mShouldSuppressNotificationDot = shouldSuppressNotificationDot;
         mShouldSuppressNotificationList = shouldSuppressNotificationList;
         mShouldSuppressPeek = shouldSuppressPeek;
@@ -115,9 +115,9 @@
         return mRanking.canBubble();
     }
 
-    /** @return true if this notification is clearable. */
-    public boolean isClearable() {
-        return mIsClearable;
+    /** @return true if this notification can be dismissed. */
+    public boolean isDismissable() {
+        return mIsDismissable;
     }
 
     /** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
index 186b9b1..9b2e263 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
@@ -27,5 +27,6 @@
     @DimenRes val desiredHeightResId: Int,
     val title: String? = null,
     val taskId: Int,
-    val locus: String? = null
+    val locus: String? = null,
+    val isDismissable: Boolean = false
 )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
index f4fa183..48d8ccf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
@@ -43,6 +43,7 @@
 private const val ATTR_TITLE = "t"
 private const val ATTR_TASK_ID = "tid"
 private const val ATTR_LOCUS = "l"
+private const val ATTR_DISMISSABLE = "d"
 
 /**
  * Writes the bubbles in xml format into given output stream.
@@ -84,6 +85,7 @@
         bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) }
         serializer.attribute(null, ATTR_TASK_ID, bubble.taskId.toString())
         bubble.locus?.let { serializer.attribute(null, ATTR_LOCUS, it) }
+        serializer.attribute(null, ATTR_DISMISSABLE, bubble.isDismissable.toString())
         serializer.endTag(null, TAG_BUBBLE)
     } catch (e: IOException) {
         throw RuntimeException(e)
@@ -142,7 +144,8 @@
             parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null,
             parser.getAttributeWithName(ATTR_TITLE),
             parser.getAttributeWithName(ATTR_TASK_ID)?.toInt() ?: INVALID_TASK_ID,
-            parser.getAttributeWithName(ATTR_LOCUS)
+            parser.getAttributeWithName(ATTR_LOCUS),
+            parser.getAttributeWithName(ATTR_DISMISSABLE)?.toBoolean() ?: false
     )
 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 96efeeb..8484013 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -96,8 +96,7 @@
 
     /**
      * Different from {@link #equals(Object)}, this method compares the basic geometry properties
-     * of two {@link DisplayLayout} objects including width, height, rotation, density, cutout and
-     * insets.
+     * of two {@link DisplayLayout} objects including width, height, rotation, density, cutout.
      * @return {@code true} if the given {@link DisplayLayout} is identical geometry wise.
      */
     public boolean isSameGeometry(@NonNull DisplayLayout other) {
@@ -105,8 +104,7 @@
                 && mHeight == other.mHeight
                 && mRotation == other.mRotation
                 && mDensityDpi == other.mDensityDpi
-                && Objects.equals(mCutout, other.mCutout)
-                && Objects.equals(mStableInsets, other.mStableInsets);
+                && Objects.equals(mCutout, other.mCutout);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 4f33a71..06f0a70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -16,11 +16,12 @@
 
 package com.android.wm.shell.compatui;
 
+import android.annotation.NonNull;
+import android.app.TaskInfo;
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.provider.DeviceConfig;
 
-import androidx.annotation.NonNull;
-
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.annotations.ShellMainThread;
@@ -34,11 +35,27 @@
 @WMSingleton
 public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener {
 
-    static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog";
+    private static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG =
+            "enable_letterbox_restart_confirmation_dialog";
 
-    static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
+    private static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
             "enable_letterbox_reachability_education";
 
+    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG = true;
+
+    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION = false;
+
+    /**
+     * The name of the {@link SharedPreferences} that holds which user has seen the Restart
+     * confirmation dialog.
+     */
+    private static final String DONT_SHOW_RESTART_DIALOG_PREF_NAME = "dont_show_restart_dialog";
+
+    /**
+     * The {@link SharedPreferences} instance for {@link #DONT_SHOW_RESTART_DIALOG_PREF_NAME}.
+     */
+    private final SharedPreferences mSharedPreferences;
+
     // Whether the extended restart dialog is enabled
     private boolean mIsRestartDialogEnabled;
 
@@ -64,12 +81,15 @@
         mIsReachabilityEducationEnabled = context.getResources().getBoolean(
                 R.bool.config_letterboxIsReachabilityEducationEnabled);
         mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, false);
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
+                DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
         mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION,
-                false);
+                DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION);
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor,
                 this);
+        mSharedPreferences = context.getSharedPreferences(DONT_SHOW_RESTART_DIALOG_PREF_NAME,
+                Context.MODE_PRIVATE);
     }
 
     /**
@@ -102,18 +122,37 @@
         mIsReachabilityEducationOverrideEnabled = enabled;
     }
 
+    boolean getDontShowRestartDialogAgain(TaskInfo taskInfo) {
+        final int userId = taskInfo.userId;
+        final String packageName = taskInfo.topActivity.getPackageName();
+        return mSharedPreferences.getBoolean(
+                getDontShowAgainRestartKey(userId, packageName), /* default= */ false);
+    }
+
+    void setDontShowRestartDialogAgain(TaskInfo taskInfo) {
+        final int userId = taskInfo.userId;
+        final String packageName = taskInfo.topActivity.getPackageName();
+        mSharedPreferences.edit().putBoolean(getDontShowAgainRestartKey(userId, packageName),
+                true).apply();
+    }
+
     @Override
     public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
-        // TODO(b/263349751): Update flag and default value to true
         if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_RESTART_DIALOG)) {
             mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
                     DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
-                    false);
+                    DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
         }
+        // TODO(b/263349751): Update flag and default value to true
         if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION)) {
             mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
                     DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                    KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false);
+                    KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION,
+                    DEFAULT_VALUE_ENABLE_LETTERBOX_REACHABILITY_EDUCATION);
         }
     }
-}
+
+    private String getDontShowAgainRestartKey(int userId, String packageName) {
+        return packageName + "@" + userId;
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 6627de5..3b2db51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -24,6 +24,7 @@
 import android.hardware.display.DisplayManager;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.InsetsSourceControl;
@@ -49,6 +50,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -91,6 +93,18 @@
     private final SparseArray<CompatUIWindowManager> mActiveCompatLayouts = new SparseArray<>(0);
 
     /**
+     * {@link SparseArray} that maps task ids to {@link RestartDialogWindowManager} that are
+     * currently visible
+     */
+    private final SparseArray<RestartDialogWindowManager> mTaskIdToRestartDialogWindowManagerMap =
+            new SparseArray<>(0);
+
+    /**
+     * {@link Set} of task ids for which we need to display a restart confirmation dialog
+     */
+    private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>();
+
+    /**
      * The active Letterbox Education layout if there is one (there can be at most one active).
      *
      * <p>An active layout is a layout that is eligible to be shown for the associated task but
@@ -111,12 +125,15 @@
     private final ShellExecutor mMainExecutor;
     private final Lazy<Transitions> mTransitionsLazy;
     private final DockStateReader mDockStateReader;
+    private final CompatUIConfiguration mCompatUIConfiguration;
 
     private CompatUICallback mCallback;
 
     // Only show each hint once automatically in the process life.
     private final CompatUIHintsState mCompatUIHintsState;
 
+    private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;
+
     // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
     // be shown.
     private boolean mKeyguardShowing;
@@ -130,7 +147,9 @@
             SyncTransactionQueue syncQueue,
             ShellExecutor mainExecutor,
             Lazy<Transitions> transitionsLazy,
-            DockStateReader dockStateReader) {
+            DockStateReader dockStateReader,
+            CompatUIConfiguration compatUIConfiguration,
+            CompatUIShellCommandHandler compatUIShellCommandHandler) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -140,14 +159,17 @@
         mMainExecutor = mainExecutor;
         mTransitionsLazy = transitionsLazy;
         mCompatUIHintsState = new CompatUIHintsState();
-        shellInit.addInitCallback(this::onInit, this);
         mDockStateReader = dockStateReader;
+        mCompatUIConfiguration = compatUIConfiguration;
+        mCompatUIShellCommandHandler = compatUIShellCommandHandler;
+        shellInit.addInitCallback(this::onInit, this);
     }
 
     private void onInit() {
         mShellController.addKeyguardChangeListener(this);
         mDisplayController.addDisplayWindowListener(this);
         mImeController.addPositionProcessor(this);
+        mCompatUIShellCommandHandler.onInit();
     }
 
     /** Sets the callback for UI interactions. */
@@ -164,6 +186,9 @@
      */
     public void onCompatInfoChanged(TaskInfo taskInfo,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+        if (taskInfo != null && !taskInfo.topActivityInSizeCompat) {
+            mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
+        }
         if (taskInfo.configuration == null || taskListener == null) {
             // Null token means the current foreground activity is not in compatibility mode.
             removeLayouts(taskInfo.taskId);
@@ -172,6 +197,7 @@
 
         createOrUpdateCompatLayout(taskInfo, taskListener);
         createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
+        createOrUpdateRestartDialogLayout(taskInfo, taskListener);
     }
 
     @Override
@@ -278,7 +304,21 @@
             ShellTaskOrganizer.TaskListener taskListener) {
         return new CompatUIWindowManager(context,
                 taskInfo, mSyncQueue, mCallback, taskListener,
-                mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState);
+                mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
+                mCompatUIConfiguration, this::onRestartButtonClicked);
+    }
+
+    private void onRestartButtonClicked(
+            Pair<TaskInfo, ShellTaskOrganizer.TaskListener> taskInfoState) {
+        if (mCompatUIConfiguration.isRestartDialogEnabled()
+                && !mCompatUIConfiguration.getDontShowRestartDialogAgain(
+                taskInfoState.first)) {
+            // We need to show the dialog
+            mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
+            onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
+        } else {
+            mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
+        }
     }
 
     private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
@@ -327,6 +367,60 @@
         mActiveLetterboxEduLayout = null;
     }
 
+    private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
+            ShellTaskOrganizer.TaskListener taskListener) {
+        RestartDialogWindowManager layout =
+                mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
+        if (layout != null) {
+            // TODO(b/266262111) Handle theme change when taskListener changes
+            if (layout.getTaskListener() != taskListener) {
+                mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
+            }
+            layout.setRequestRestartDialog(
+                    mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId));
+            // UI already exists, update the UI layout.
+            if (!layout.updateCompatInfo(taskInfo, taskListener,
+                    showOnDisplay(layout.getDisplayId()))) {
+                // The layout is no longer eligible to be shown, remove from active layouts.
+                mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
+            }
+            return;
+        }
+        // Create a new UI layout.
+        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
+        if (context == null) {
+            return;
+        }
+        layout = createRestartDialogWindowManager(context, taskInfo, taskListener);
+        layout.setRequestRestartDialog(
+                mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId));
+        if (layout.createLayout(showOnDisplay(taskInfo.displayId))) {
+            // The new layout is eligible to be shown, add it the active layouts.
+            mTaskIdToRestartDialogWindowManagerMap.put(taskInfo.taskId, layout);
+        }
+    }
+
+    @VisibleForTesting
+    RestartDialogWindowManager createRestartDialogWindowManager(Context context, TaskInfo taskInfo,
+            ShellTaskOrganizer.TaskListener taskListener) {
+        return new RestartDialogWindowManager(context, taskInfo, mSyncQueue, taskListener,
+                mDisplayController.getDisplayLayout(taskInfo.displayId), mTransitionsLazy.get(),
+                this::onRestartDialogCallback, this::onRestartDialogDismissCallback,
+                mCompatUIConfiguration);
+    }
+
+    private void onRestartDialogCallback(
+            Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
+        mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
+        mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
+    }
+
+    private void onRestartDialogDismissCallback(
+            Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
+        mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId);
+        onCompatInfoChanged(stateInfo.first, stateInfo.second);
+    }
+
     private void removeLayouts(int taskId) {
         final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
         if (layout != null) {
@@ -338,6 +432,14 @@
             mActiveLetterboxEduLayout.release();
             mActiveLetterboxEduLayout = null;
         }
+
+        final RestartDialogWindowManager restartLayout =
+                mTaskIdToRestartDialogWindowManagerMap.get(taskId);
+        if (restartLayout != null) {
+            restartLayout.release();
+            mTaskIdToRestartDialogWindowManagerMap.remove(taskId);
+            mSetOfTaskIdsShowingRestartDialog.remove(taskId);
+        }
     }
 
     private Context getOrCreateDisplayContext(int displayId) {
@@ -382,6 +484,14 @@
         if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) {
             callback.accept(mActiveLetterboxEduLayout);
         }
+        for (int i = 0; i < mTaskIdToRestartDialogWindowManagerMap.size(); i++) {
+            final int taskId = mTaskIdToRestartDialogWindowManagerMap.keyAt(i);
+            final RestartDialogWindowManager layout =
+                    mTaskIdToRestartDialogWindowManagerMap.get(taskId);
+            if (layout != null && condition.test(layout)) {
+                callback.accept(layout);
+            }
+        }
     }
 
     /** An implementation of {@link OnInsetsChangedListener} for a given display id. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index bce3ec4..fe95d04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -21,12 +21,14 @@
 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.Log;
+import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 
@@ -38,6 +40,8 @@
 import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
 import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
 
+import java.util.function.Consumer;
+
 /**
  * Window manager for the Size Compat restart button and Camera Compat control.
  */
@@ -50,6 +54,13 @@
 
     private final CompatUICallback mCallback;
 
+    private final CompatUIConfiguration mCompatUIConfiguration;
+
+    private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
+
+    @NonNull
+    private TaskInfo mTaskInfo;
+
     // Remember the last reported states in case visibility changes due to keyguard or IME updates.
     @VisibleForTesting
     boolean mHasSizeCompat;
@@ -68,12 +79,16 @@
     CompatUIWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, CompatUICallback callback,
             ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
-            CompatUIHintsState compatUIHintsState) {
+            CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration,
+            Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
+        mTaskInfo = taskInfo;
         mCallback = callback;
         mHasSizeCompat = taskInfo.topActivityInSizeCompat;
         mCameraCompatControlState = taskInfo.cameraCompatControlState;
         mCompatUIHintsState = compatUIHintsState;
+        mCompatUIConfiguration = compatUIConfiguration;
+        mOnRestartButtonClicked = onRestartButtonClicked;
     }
 
     @Override
@@ -119,6 +134,7 @@
     @Override
     public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
             boolean canShow) {
+        mTaskInfo = taskInfo;
         final boolean prevHasSizeCompat = mHasSizeCompat;
         final int prevCameraCompatControlState = mCameraCompatControlState;
         mHasSizeCompat = taskInfo.topActivityInSizeCompat;
@@ -138,7 +154,7 @@
 
     /** Called when the restart button is clicked. */
     void onRestartButtonClicked() {
-        mCallback.onSizeCompatRestartButtonClicked(mTaskId);
+        mOnRestartButtonClicked.accept(Pair.create(mTaskInfo, getTaskListener()));
     }
 
     /** Called when the camera treatment button is clicked. */
@@ -199,8 +215,14 @@
                 : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
         final int positionY = taskStableBounds.bottom - taskBounds.top
                 - mLayout.getMeasuredHeight();
-
+        // To secure a proper visualisation, we hide the layout while updating the position of
+        // the {@link SurfaceControl} it belongs.
+        final int oldVisibility = mLayout.getVisibility();
+        if (oldVisibility == View.VISIBLE) {
+            mLayout.setVisibility(View.GONE);
+        }
         updateSurfacePosition(positionX, positionY);
+        mLayout.setVisibility(oldVisibility);
     }
 
     private void updateVisibilityOfViews() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index face243..db87f657 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -151,6 +151,7 @@
     @Override
     public void setConfiguration(Configuration configuration) {
         super.setConfiguration(configuration);
+        // TODO(b/266262111): Investigate loss of theme configuration when switching TaskListener
         mContext = mContext.createConfigurationContext(configuration);
     }
 
@@ -169,6 +170,10 @@
         initSurface(mLeash);
     }
 
+    protected ShellTaskOrganizer.TaskListener getTaskListener() {
+        return mTaskListener;
+    }
+
     /** Inits the z-order of the surface. */
     private void initSurface(SurfaceControl leash) {
         final int z = getZOrder();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java
new file mode 100644
index 0000000..c53e638
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogLayout.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.compatui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.wm.shell.R;
+
+import java.util.function.Consumer;
+
+/**
+ * Container for a SCM restart confirmation dialog and background dim.
+ */
+public class RestartDialogLayout extends ConstraintLayout implements DialogContainerSupplier {
+
+    private View mDialogContainer;
+    private TextView mDialogTitle;
+    private Drawable mBackgroundDim;
+
+    public RestartDialogLayout(Context context) {
+        this(context, null);
+    }
+
+    public RestartDialogLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public View getDialogContainerView() {
+        return mDialogContainer;
+    }
+
+    TextView getDialogTitle() {
+        return mDialogTitle;
+    }
+
+    @Override
+    public Drawable getBackgroundDimDrawable() {
+        return mBackgroundDim;
+    }
+
+    /**
+     * Register a callback for the dismiss button and background dim.
+     *
+     * @param callback The callback to register or null if all on click listeners should be removed.
+     */
+    void setDismissOnClickListener(@Nullable Runnable callback) {
+        final OnClickListener listener = callback == null ? null : view -> callback.run();
+        findViewById(R.id.letterbox_restart_dialog_dismiss_button).setOnClickListener(listener);
+    }
+
+    /**
+     * Register a callback for the restart button
+     *
+     * @param callback The callback to register or null if all on click listeners should be removed.
+     */
+    void setRestartOnClickListener(@Nullable Consumer<Boolean> callback) {
+        final CheckBox dontShowAgainCheckbox = findViewById(R.id.letterbox_restart_dialog_checkbox);
+        final OnClickListener listener = callback == null ? null : view -> callback.accept(
+                dontShowAgainCheckbox.isChecked());
+        findViewById(R.id.letterbox_restart_dialog_restart_button).setOnClickListener(listener);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDialogContainer = findViewById(R.id.letterbox_restart_dialog_container);
+        mDialogTitle = findViewById(R.id.letterbox_restart_dialog_title);
+        mBackgroundDim = getBackground().mutate();
+        // Set the alpha of the background dim to 0 for enter animation.
+        mBackgroundDim.setAlpha(0);
+        // We add a no-op on-click listener to the dialog container so that clicks on it won't
+        // propagate to the listener of the layout (which represents the background dim).
+        mDialogContainer.setOnClickListener(view -> {});
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
new file mode 100644
index 0000000..10f25d0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.compatui;
+
+import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.provider.Settings;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.function.Consumer;
+
+/**
+ * Window manager for the Restart Dialog.
+ *
+ * TODO(b/263484314): Create abstraction of RestartDialogWindowManager and LetterboxEduWindowManager
+ */
+class RestartDialogWindowManager extends CompatUIWindowManagerAbstract {
+
+    /**
+     * The restart dialog should be the topmost child of the Task in case there can be more
+     * than one child.
+     */
+    private static final int Z_ORDER = Integer.MAX_VALUE;
+
+    private final DialogAnimationController<RestartDialogLayout> mAnimationController;
+
+    private final Transitions mTransitions;
+
+    // Remember the last reported state in case visibility changes due to keyguard or IME updates.
+    private boolean mRequestRestartDialog;
+
+    private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback;
+
+    private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartCallback;
+
+    private final CompatUIConfiguration mCompatUIConfiguration;
+
+    /**
+     * The vertical margin between the dialog container and the task stable bounds (excluding
+     * insets).
+     */
+    private final int mDialogVerticalMargin;
+
+    @NonNull
+    private TaskInfo mTaskInfo;
+
+    @Nullable
+    @VisibleForTesting
+    RestartDialogLayout mLayout;
+
+    RestartDialogWindowManager(Context context, TaskInfo taskInfo,
+            SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+            DisplayLayout displayLayout, Transitions transitions,
+            Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback,
+            Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
+            CompatUIConfiguration compatUIConfiguration) {
+        this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
+                onRestartCallback, onDismissCallback,
+                new DialogAnimationController<>(context, "RestartDialogWindowManager"),
+                compatUIConfiguration);
+    }
+
+    @VisibleForTesting
+    RestartDialogWindowManager(Context context, TaskInfo taskInfo,
+            SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
+            DisplayLayout displayLayout, Transitions transitions,
+            Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback,
+            Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
+            DialogAnimationController<RestartDialogLayout> animationController,
+            CompatUIConfiguration compatUIConfiguration) {
+        super(context, taskInfo, syncQueue, taskListener, displayLayout);
+        mTaskInfo = taskInfo;
+        mTransitions = transitions;
+        mOnDismissCallback = onDismissCallback;
+        mOnRestartCallback = onRestartCallback;
+        mAnimationController = animationController;
+        mDialogVerticalMargin = (int) mContext.getResources().getDimension(
+                R.dimen.letterbox_restart_dialog_margin);
+        mCompatUIConfiguration = compatUIConfiguration;
+    }
+
+    @Override
+    protected int getZOrder() {
+        return Z_ORDER;
+    }
+
+    @Override
+    @Nullable
+    protected  View getLayout() {
+        return mLayout;
+    }
+
+    @Override
+    protected void removeLayout() {
+        mLayout = null;
+    }
+
+    @Override
+    protected boolean eligibleToShowLayout() {
+        // We don't show this dialog if the user has explicitly selected so clicking on a checkbox.
+        return mRequestRestartDialog && !isTaskbarEduShowing() && (mLayout != null
+                || !mCompatUIConfiguration.getDontShowRestartDialogAgain(mTaskInfo));
+    }
+
+    @Override
+    protected View createLayout() {
+        mLayout = inflateLayout();
+        updateDialogMargins();
+
+        // startEnterAnimation will be called immediately if shell-transitions are disabled.
+        mTransitions.runOnIdle(this::startEnterAnimation);
+
+        return mLayout;
+    }
+
+    void setRequestRestartDialog(boolean enabled) {
+        mRequestRestartDialog = enabled;
+    }
+
+    @Override
+    public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
+            boolean canShow) {
+        mTaskInfo = taskInfo;
+        return super.updateCompatInfo(taskInfo, taskListener, canShow);
+    }
+
+    private void updateDialogMargins() {
+        if (mLayout == null) {
+            return;
+        }
+        final View dialogContainer = mLayout.getDialogContainerView();
+        ViewGroup.MarginLayoutParams marginParams =
+                (ViewGroup.MarginLayoutParams) dialogContainer.getLayoutParams();
+
+        final Rect taskBounds = getTaskBounds();
+        final Rect taskStableBounds = getTaskStableBounds();
+
+        marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin;
+        marginParams.bottomMargin =
+                taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin;
+        dialogContainer.setLayoutParams(marginParams);
+    }
+
+    private RestartDialogLayout inflateLayout() {
+        return (RestartDialogLayout) LayoutInflater.from(mContext).inflate(
+                R.layout.letterbox_restart_dialog_layout, null);
+    }
+
+    private void startEnterAnimation() {
+        if (mLayout == null) {
+            // Dialog has already been released.
+            return;
+        }
+        mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
+                this::onDialogEnterAnimationEnded);
+    }
+
+    private void onDialogEnterAnimationEnded() {
+        if (mLayout == null) {
+            // Dialog has already been released.
+            return;
+        }
+        mLayout.setDismissOnClickListener(this::onDismiss);
+        mLayout.setRestartOnClickListener(dontShowAgain -> {
+            if (mLayout != null) {
+                mLayout.setDismissOnClickListener(null);
+                mAnimationController.startExitAnimation(mLayout, () -> {
+                    release();
+                });
+            }
+            if (dontShowAgain) {
+                mCompatUIConfiguration.setDontShowRestartDialogAgain(mTaskInfo);
+            }
+            mOnRestartCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
+        });
+        // Focus on the dialog title for accessibility.
+        mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+    }
+
+    private void onDismiss() {
+        if (mLayout == null) {
+            return;
+        }
+
+        mLayout.setDismissOnClickListener(null);
+        mAnimationController.startExitAnimation(mLayout, () -> {
+            release();
+            mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
+        });
+    }
+
+    @Override
+    public void release() {
+        mAnimationController.cancelAnimation();
+        super.release();
+    }
+
+    @Override
+    protected void onParentBoundsChanged() {
+        if (mLayout == null) {
+            return;
+        }
+        // Both the layout dimensions and dialog margins depend on the parent bounds.
+        WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams();
+        mLayout.setLayoutParams(windowLayoutParams);
+        updateDialogMargins();
+        relayout(windowLayoutParams);
+    }
+
+    @Override
+    protected void updateSurfacePosition() {
+        // Nothing to do, since the position of the surface is fixed to the top left corner (0,0)
+        // of the task (parent surface), which is the default position of a surface.
+    }
+
+    @Override
+    protected WindowManager.LayoutParams getWindowLayoutParams() {
+        final Rect taskBounds = getTaskBounds();
+        return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */
+                taskBounds.height());
+    }
+
+    @VisibleForTesting
+    boolean isTaskbarEduShowing() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                LAUNCHER_TASKBAR_EDUCATION_SHOWING, /* def= */ 0) == 1;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 8022e9b..94db878 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -39,6 +39,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
 import com.android.wm.shell.pip.tv.TvPipBoundsController;
 import com.android.wm.shell.pip.tv.TvPipBoundsState;
@@ -69,6 +70,7 @@
             ShellInit shellInit,
             ShellController shellController,
             TvPipBoundsState tvPipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             TvPipBoundsController tvPipBoundsController,
             PipAppOpsListener pipAppOpsListener,
@@ -88,6 +90,7 @@
                         shellInit,
                         shellController,
                         tvPipBoundsState,
+                        pipSizeSpecHandler,
                         tvPipBoundsAlgorithm,
                         tvPipBoundsController,
                         pipAppOpsListener,
@@ -127,14 +130,23 @@
     @WMSingleton
     @Provides
     static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
-            TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
-        return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm);
+            TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+            PipSizeSpecHandler pipSizeSpecHandler) {
+        return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
+                pipSizeSpecHandler);
     }
 
     @WMSingleton
     @Provides
-    static TvPipBoundsState provideTvPipBoundsState(Context context) {
-        return new TvPipBoundsState(context);
+    static TvPipBoundsState provideTvPipBoundsState(Context context,
+            PipSizeSpecHandler pipSizeSpecHandler) {
+        return new TvPipBoundsState(context, pipSizeSpecHandler);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
+        return new PipSizeSpecHandler(context);
     }
 
     // Handler needed for loadDrawableAsync() in PipControlsViewController
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 09f5cf1..25c430c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -56,7 +56,9 @@
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.compatui.CompatUIConfiguration;
 import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
 import com.android.wm.shell.desktopmode.DesktopMode;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
@@ -196,10 +198,11 @@
             DisplayController displayController, DisplayInsetsController displayInsetsController,
             DisplayImeController imeController, SyncTransactionQueue syncQueue,
             @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
-            DockStateReader dockStateReader) {
+            DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
+            CompatUIShellCommandHandler compatUIShellCommandHandler) {
         return new CompatUIController(context, shellInit, shellController, displayController,
                 displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
-                dockStateReader);
+                dockStateReader, compatUIConfiguration, compatUIShellCommandHandler);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 512a4ef..1135aa3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -77,6 +77,7 @@
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -338,6 +339,7 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
             PipBoundsState pipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController,
@@ -354,17 +356,18 @@
         return Optional.ofNullable(PipController.create(
                 context, shellInit, shellCommandHandler, shellController,
                 displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
-                pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
-                phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler,
-                pipTransitionController, windowManagerShellWrapper, taskStackListener,
-                pipParamsChangedForwarder, displayInsetsController, oneHandedController,
-                mainExecutor));
+                pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipMotionHelper,
+                pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
+                pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
+                taskStackListener, pipParamsChangedForwarder, displayInsetsController,
+                oneHandedController, mainExecutor));
     }
 
     @WMSingleton
     @Provides
-    static PipBoundsState providePipBoundsState(Context context) {
-        return new PipBoundsState(context);
+    static PipBoundsState providePipBoundsState(Context context,
+            PipSizeSpecHandler pipSizeSpecHandler) {
+        return new PipBoundsState(context, pipSizeSpecHandler);
     }
 
     @WMSingleton
@@ -381,11 +384,18 @@
 
     @WMSingleton
     @Provides
+    static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
+        return new PipSizeSpecHandler(context);
+    }
+
+    @WMSingleton
+    @Provides
     static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
             PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
-            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm) {
+            PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+            PipSizeSpecHandler pipSizeSpecHandler) {
         return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
-                pipKeepClearAlgorithm);
+                pipKeepClearAlgorithm, pipSizeSpecHandler);
     }
 
     // Handler is used by Icon.loadDrawableAsync
@@ -409,13 +419,14 @@
             PhonePipMenuController menuPhoneController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipBoundsState pipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             PipTaskOrganizer pipTaskOrganizer,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
             PipUiEventLogger pipUiEventLogger,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
-                pipBoundsState, pipTaskOrganizer, pipMotionHelper,
+                pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
                 floatingContentCoordinator, pipUiEventLogger, mainExecutor);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 63b03ab..a839a23 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -36,6 +36,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArraySet;
@@ -261,6 +262,11 @@
         }
     }
 
+    /** Get number of tasks that are marked as visible */
+    int getVisibleTaskCount() {
+        return mDesktopModeTaskRepository.getVisibleTaskCount();
+    }
+
     @NonNull
     private WindowContainerTransaction bringDesktopAppsToFront(boolean force) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -438,5 +444,15 @@
             executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
                     DesktopModeController::showDesktopApps);
         }
+
+        @Override
+        public int getVisibleTaskCount() throws RemoteException {
+            int[] result = new int[1];
+            executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
+                    controller -> result[0] = controller.getVisibleTaskCount(),
+                    true /* blocking */
+            );
+            return result[0];
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 600ccc1..47342c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -143,6 +143,13 @@
     }
 
     /**
+     * Get number of tasks that are marked as visible
+     */
+    fun getVisibleTaskCount(): Int {
+        return visibleTasks.size
+    }
+
+    /**
      * Add (or move if it already exists) the task to the top of the ordered list.
      */
     fun addOrMoveFreeformTaskToTop(taskId: Int) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 9165f70..2303325 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -96,6 +96,11 @@
         }
     }
 
+    /** Get number of tasks that are marked as visible */
+    fun getVisibleTaskCount(): Int {
+        return desktopModeTaskRepository.getVisibleTaskCount()
+    }
+
     /** Move a task with given `taskId` to desktop */
     fun moveToDesktop(taskId: Int) {
         shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task) }
@@ -309,5 +314,16 @@
                 Consumer(DesktopTasksController::showDesktopApps)
             )
         }
+
+        override fun getVisibleTaskCount(): Int {
+            val result = IntArray(1)
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                controller,
+                "getVisibleTaskCount",
+                { controller -> result[0] = controller.getVisibleTaskCount() },
+                true /* blocking */
+            )
+            return result[0]
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index 5042bd6..d0739e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -23,4 +23,7 @@
 
     /** Show apps on the desktop */
     void showDesktopApps();
+
+    /** Get count of visible desktop tasks */
+    int getVisibleTaskCount();
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index b9caf62..26f47fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -114,7 +114,9 @@
                 t.setPosition(leash, positionInParent.x, positionInParent.y);
                 t.setAlpha(leash, 1f);
                 t.setMatrix(leash, 1, 0, 0, 1);
-                t.show(leash);
+                if (taskInfo.isVisible) {
+                    t.show(leash);
+                }
             });
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index f6d67d8..867162b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -16,24 +16,19 @@
 
 package com.android.wm.shell.pip;
 
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PictureInPictureParams;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.util.Size;
-import android.util.TypedValue;
 import android.view.Gravity;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 
 import java.io.PrintWriter;
 
@@ -45,33 +40,29 @@
     private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
     private static final float INVALID_SNAP_FRACTION = -1f;
 
-    private final @NonNull PipBoundsState mPipBoundsState;
+    @NonNull private final PipBoundsState mPipBoundsState;
+    @NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler;
     private final PipSnapAlgorithm mSnapAlgorithm;
     private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
 
-    private float mDefaultSizePercent;
-    private float mMinAspectRatioForMinSize;
-    private float mMaxAspectRatioForMinSize;
     private float mDefaultAspectRatio;
     private float mMinAspectRatio;
     private float mMaxAspectRatio;
     private int mDefaultStackGravity;
-    private int mDefaultMinSize;
-    private int mOverridableMinSize;
-    protected Point mScreenEdgeInsets;
 
     public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
             @NonNull PipSnapAlgorithm pipSnapAlgorithm,
-            @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm) {
+            @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
+            @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
         mPipBoundsState = pipBoundsState;
         mSnapAlgorithm = pipSnapAlgorithm;
         mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
+        mPipSizeSpecHandler = pipSizeSpecHandler;
         reloadResources(context);
         // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
         // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
         // triggers a configuration change and the resources to be reloaded.
         mPipBoundsState.setAspectRatio(mDefaultAspectRatio);
-        mPipBoundsState.setMinEdgeSize(mDefaultMinSize);
     }
 
     /**
@@ -83,27 +74,15 @@
                 R.dimen.config_pictureInPictureDefaultAspectRatio);
         mDefaultStackGravity = res.getInteger(
                 R.integer.config_defaultPictureInPictureGravity);
-        mDefaultMinSize = res.getDimensionPixelSize(
-                R.dimen.default_minimal_size_pip_resizable_task);
-        mOverridableMinSize = res.getDimensionPixelSize(
-                R.dimen.overridable_minimal_size_pip_resizable_task);
         final String screenEdgeInsetsDpString = res.getString(
                 R.string.config_defaultPictureInPictureScreenEdgeInsets);
         final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
                 ? Size.parseSize(screenEdgeInsetsDpString)
                 : null;
-        mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
-                : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
-                        dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
         mMinAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
         mMaxAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
-        mDefaultSizePercent = res.getFloat(
-                R.dimen.config_pictureInPictureDefaultSizePercent);
-        mMaxAspectRatioForMinSize = res.getFloat(
-                R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
-        mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
     }
 
     /**
@@ -180,8 +159,9 @@
         if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
             // If either dimension is smaller than the allowed minimum, adjust them
             // according to mOverridableMinSize
-            return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize),
-                    Math.max(windowLayout.minHeight, mOverridableMinSize));
+            return new Size(
+                    Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()),
+                    Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize()));
         }
         return null;
     }
@@ -243,28 +223,13 @@
         final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
                 getMovementBounds(stackBounds), mPipBoundsState.getStashedState());
 
-        final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
         final Size size;
         if (useCurrentMinEdgeSize || useCurrentSize) {
-            // The default minimum edge size, or the override min edge size if set.
-            final int defaultMinEdgeSize = overrideMinSize == null ? mDefaultMinSize
-                    : mPipBoundsState.getOverrideMinEdgeSize();
-            final int minEdgeSize = useCurrentMinEdgeSize ? mPipBoundsState.getMinEdgeSize()
-                    : defaultMinEdgeSize;
-            // Use the existing size but adjusted to the aspect ratio and min edge size.
-            size = getSizeForAspectRatio(
-                    new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize);
+            // Use the existing size but adjusted to the new aspect ratio.
+            size = mPipSizeSpecHandler.getSizeForAspectRatio(
+                    new Size(stackBounds.width(), stackBounds.height()), aspectRatio);
         } else {
-            if (overrideMinSize != null) {
-                // The override minimal size is set, use that as the default size making sure it's
-                // adjusted to the aspect ratio.
-                size = adjustSizeToAspectRatio(overrideMinSize, aspectRatio);
-            } else {
-                // Calculate the default size using the display size and default min edge size.
-                final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
-                size = getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
-                        displayLayout.width(), displayLayout.height());
-            }
+            size = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
         }
 
         final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
@@ -273,18 +238,6 @@
         mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
     }
 
-    /** Adjusts the given size to conform to the given aspect ratio. */
-    private Size adjustSizeToAspectRatio(@NonNull Size size, float aspectRatio) {
-        final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
-        if (sizeAspectRatio > aspectRatio) {
-            // Size is wider, fix the width and increase the height
-            return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
-        } else {
-            // Size is taller, fix the height and adjust the width.
-            return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
-        }
-    }
-
     /**
      * @return the default bounds to show the PIP, if a {@param snapFraction} and {@param size} are
      * provided, then it will apply the default bounds to the provided snap fraction and size.
@@ -303,17 +256,9 @@
         final Size defaultSize;
         final Rect insetBounds = new Rect();
         getInsetBounds(insetBounds);
-        final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
-        final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
-        if (overrideMinSize != null) {
-            // The override minimal size is set, use that as the default size making sure it's
-            // adjusted to the aspect ratio.
-            defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio);
-        } else {
-            // Calculate the default size using the display size and default min edge size.
-            defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
-                    mDefaultMinSize, displayLayout.width(), displayLayout.height());
-        }
+
+        // Calculate the default size
+        defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio);
 
         // Now that we have the default size, apply the snap fraction if valid or position the
         // bounds using the default gravity.
@@ -335,12 +280,7 @@
      * Populates the bounds on the screen that the PIP can be visible in.
      */
     public void getInsetBounds(Rect outRect) {
-        final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
-        Rect insets = mPipBoundsState.getDisplayLayout().stableInsets();
-        outRect.set(insets.left + mScreenEdgeInsets.x,
-                insets.top + mScreenEdgeInsets.y,
-                displayLayout.width() - insets.right - mScreenEdgeInsets.x,
-                displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+        outRect.set(mPipSizeSpecHandler.getInsetBounds());
     }
 
     /**
@@ -405,71 +345,11 @@
         mSnapAlgorithm.applySnapFraction(stackBounds, movementBounds, snapFraction);
     }
 
-    public int getDefaultMinSize() {
-        return mDefaultMinSize;
-    }
-
     /**
      * @return the pixels for a given dp value.
      */
     private int dpToPx(float dpValue, DisplayMetrics dm) {
-        return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
-    }
-
-    /**
-     * @return the size of the PiP at the given aspectRatio, ensuring that the minimum edge
-     * is at least minEdgeSize.
-     */
-    public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
-            int displayHeight) {
-        final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
-        final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
-
-        final int width;
-        final int height;
-        if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
-            // Beyond these points, we can just use the min size as the shorter edge
-            if (aspectRatio <= 1) {
-                // Portrait, width is the minimum size
-                width = minSize;
-                height = Math.round(width / aspectRatio);
-            } else {
-                // Landscape, height is the minimum size
-                height = minSize;
-                width = Math.round(height * aspectRatio);
-            }
-        } else {
-            // Within these points, we ensure that the bounds fit within the radius of the limits
-            // at the points
-            final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
-            final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
-            height = (int) Math.round(Math.sqrt((radius * radius)
-                    / (aspectRatio * aspectRatio + 1)));
-            width = Math.round(height * aspectRatio);
-        }
-        return new Size(width, height);
-    }
-
-    /**
-     * @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
-     * minimum edge is at least minEdgeSize.
-     */
-    public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
-        final int smallestSize = Math.min(size.getWidth(), size.getHeight());
-        final int minSize = (int) Math.max(minEdgeSize, smallestSize);
-
-        final int width;
-        final int height;
-        if (aspectRatio <= 1) {
-            // Portrait, width is the minimum size.
-            width = minSize;
-            height = Math.round(width / aspectRatio);
-        } else {
-            // Landscape, height is the minimum size
-            height = minSize;
-            width = Math.round(height * aspectRatio);
-        }
-        return new Size(width, height);
+        return PipUtils.dpToPx(dpValue, dm);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 5376ae3..61da10b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -37,6 +37,7 @@
 import com.android.internal.util.function.TriConsumer;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.io.PrintWriter;
@@ -83,13 +84,10 @@
     private int mStashedState = STASH_TYPE_NONE;
     private int mStashOffset;
     private @Nullable PipReentryState mPipReentryState;
+    private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
     private @Nullable ComponentName mLastPipComponentName;
     private int mDisplayId = Display.DEFAULT_DISPLAY;
     private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
-    /** The current minimum edge size of PIP. */
-    private int mMinEdgeSize;
-    /** The preferred minimum (and default) size specified by apps. */
-    private @Nullable Size mOverrideMinSize;
     private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
     private boolean mIsImeShowing;
     private int mImeHeight;
@@ -122,9 +120,10 @@
     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
     private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
 
-    public PipBoundsState(@NonNull Context context) {
+    public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler) {
         mContext = context;
         reloadResources();
+        mPipSizeSpecHandler = pipSizeSpecHandler;
     }
 
     /** Reloads the resources. */
@@ -323,20 +322,10 @@
         mPipReentryState = null;
     }
 
-    /** Set the PIP minimum edge size. */
-    public void setMinEdgeSize(int minEdgeSize) {
-        mMinEdgeSize = minEdgeSize;
-    }
-
-    /** Returns the PIP's current minimum edge size. */
-    public int getMinEdgeSize() {
-        return mMinEdgeSize;
-    }
-
     /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
     public void setOverrideMinSize(@Nullable Size overrideMinSize) {
-        final boolean changed = !Objects.equals(overrideMinSize, mOverrideMinSize);
-        mOverrideMinSize = overrideMinSize;
+        final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize());
+        mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize);
         if (changed && mOnMinimalSizeChangeCallback != null) {
             mOnMinimalSizeChangeCallback.run();
         }
@@ -345,13 +334,12 @@
     /** Returns the preferred minimal size specified by the activity in PIP. */
     @Nullable
     public Size getOverrideMinSize() {
-        return mOverrideMinSize;
+        return mPipSizeSpecHandler.getOverrideMinSize();
     }
 
     /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
     public int getOverrideMinEdgeSize() {
-        if (mOverrideMinSize == null) return 0;
-        return Math.min(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight());
+        return mPipSizeSpecHandler.getOverrideMinEdgeSize();
     }
 
     /** Get the state of the bounds in motion. */
@@ -581,11 +569,8 @@
         pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
         pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
         pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
-        pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
         pw.println(innerPrefix + "mStashedState=" + mStashedState);
         pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
-        pw.println(innerPrefix + "mMinEdgeSize=" + mMinEdgeSize);
-        pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
         pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
         pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
         pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 7096a64..283b1ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
@@ -41,6 +42,11 @@
         }
     }
 
+    @Nullable
+    public SurfaceControl getLeash() {
+        return mLeash;
+    }
+
     /**
      * Animates the internal {@link #mLeash} by a given fraction.
      * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index fa00619..8b98790 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
 import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
@@ -26,8 +27,10 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.RemoteException;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
+import android.util.TypedValue;
 import android.window.TaskSnapshot;
 
 import com.android.internal.protolog.common.ProtoLog;
@@ -70,6 +73,13 @@
     }
 
     /**
+     * @return the pixels for a given dp value.
+     */
+    public static int dpToPx(float dpValue, DisplayMetrics dm) {
+        return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
+    }
+
+    /**
      * @return true if the aspect ratios differ
      */
     public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index e83854e..d86468a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -137,6 +137,7 @@
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
     private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
     private PipBoundsState mPipBoundsState;
+    private PipSizeSpecHandler mPipSizeSpecHandler;
     private PipMotionHelper mPipMotionHelper;
     private PipTouchHandler mTouchHandler;
     private PipTransitionController mPipTransitionController;
@@ -380,6 +381,7 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
             PipBoundsState pipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController,
@@ -401,11 +403,11 @@
 
         return new PipController(context, shellInit, shellCommandHandler, shellController,
                 displayController, pipAnimationController, pipAppOpsListener,
-                pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
-                pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
-                pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
-                taskStackListener, pipParamsChangedForwarder, displayInsetsController,
-                oneHandedController, mainExecutor)
+                pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
+                pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+                pipTransitionState, pipTouchHandler, pipTransitionController,
+                windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+                displayInsetsController, oneHandedController, mainExecutor)
                 .mImpl;
     }
 
@@ -419,6 +421,7 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController,
@@ -444,6 +447,7 @@
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
         mPipBoundsState = pipBoundsState;
+        mPipSizeSpecHandler = pipSizeSpecHandler;
         mPipMotionHelper = pipMotionHelper;
         mPipTaskOrganizer = pipTaskOrganizer;
         mPipTransitionState = pipTransitionState;
@@ -512,7 +516,10 @@
         // Ensure that we have the display info in case we get calls to update the bounds before the
         // listener calls back
         mPipBoundsState.setDisplayId(mContext.getDisplayId());
-        mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
+
+        DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+        mPipSizeSpecHandler.setDisplayLayout(layout);
+        mPipBoundsState.setDisplayLayout(layout);
 
         try {
             mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener);
@@ -616,7 +623,7 @@
                             return;
                         }
                         int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
-                        onDisplayChanged(
+                        onDisplayChangedUncheck(
                                 mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
                                 false /* saveRestoreSnapFraction */);
                         int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
@@ -686,6 +693,7 @@
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
         mTouchHandler.onConfigurationChanged();
         mPipBoundsState.onConfigurationChanged();
+        mPipSizeSpecHandler.onConfigurationChanged();
     }
 
     @Override
@@ -702,13 +710,20 @@
     }
 
     private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) {
-        if (mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
-            return;
+        if (!mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
+            onDisplayChangedUncheck(layout, saveRestoreSnapFraction);
         }
+    }
+
+    private void onDisplayChangedUncheck(DisplayLayout layout, boolean saveRestoreSnapFraction) {
         Runnable updateDisplayLayout = () -> {
             final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
                     && mPipBoundsState.getDisplayLayout().rotation() != layout.rotation();
+
+            // update the internal state of objects subscribed to display changes
+            mPipSizeSpecHandler.setDisplayLayout(layout);
             mPipBoundsState.setDisplayLayout(layout);
+
             final WindowContainerTransaction wct =
                     fromRotation ? new WindowContainerTransaction() : null;
             updateMovementBounds(null /* toBounds */,
@@ -1080,6 +1095,7 @@
         mPipTaskOrganizer.dump(pw, innerPrefix);
         mPipBoundsState.dump(pw, innerPrefix);
         mPipInputConsumer.dump(pw, innerPrefix);
+        mPipSizeSpecHandler.dump(pw, innerPrefix);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
new file mode 100644
index 0000000..e787ed9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.pip.phone;
+
+import static com.android.wm.shell.pip.PipUtils.dpToPx;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.SystemProperties;
+import android.util.Size;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.io.PrintWriter;
+
+/**
+ * Acts as a source of truth for appropriate size spec for PIP.
+ */
+public class PipSizeSpecHandler {
+    private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
+
+    @NonNull private final DisplayLayout mDisplayLayout = new DisplayLayout();
+
+    @VisibleForTesting
+    final SizeSpecSource mSizeSpecSourceImpl;
+
+    /** The preferred minimum (and default minimum) size specified by apps. */
+    @Nullable private Size mOverrideMinSize;
+    private int mOverridableMinSize;
+
+    /** Used to store values obtained from resource files. */
+    private Point mScreenEdgeInsets;
+    private float mMinAspectRatioForMinSize;
+    private float mMaxAspectRatioForMinSize;
+    private int mDefaultMinSize;
+
+    @NonNull private final Context mContext;
+
+    private interface SizeSpecSource {
+        /** Returns max size allowed for the PIP window */
+        Size getMaxSize(float aspectRatio);
+
+        /** Returns default size for the PIP window */
+        Size getDefaultSize(float aspectRatio);
+
+        /** Returns min size allowed for the PIP window */
+        Size getMinSize(float aspectRatio);
+
+        /** Returns the adjusted size based on current size and target aspect ratio */
+        Size getSizeForAspectRatio(Size size, float aspectRatio);
+
+        /** Updates internal resources on configuration changes */
+        default void reloadResources() {}
+    }
+
+    /**
+     * Determines PIP window size optimized for large screens and aspect ratios close to 1:1
+     */
+    private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource {
+        private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16;
+
+        /** Default and minimum percentages for the PIP size logic. */
+        private final float mDefaultSizePercent;
+        private final float mMinimumSizePercent;
+
+        /** Aspect ratio that the PIP size spec logic optimizes for. */
+        private float mOptimizedAspectRatio;
+
+        private SizeSpecLargeScreenOptimizedImpl() {
+            mDefaultSizePercent = Float.parseFloat(SystemProperties
+                    .get("com.android.wm.shell.pip.phone.def_percentage", "0.6"));
+            mMinimumSizePercent = Float.parseFloat(SystemProperties
+                    .get("com.android.wm.shell.pip.phone.min_percentage", "0.5"));
+        }
+
+        @Override
+        public void reloadResources() {
+            final Resources res = mContext.getResources();
+
+            mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio);
+            // make sure the optimized aspect ratio is valid with a default value to fall back to
+            if (mOptimizedAspectRatio > 1) {
+                mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO;
+            }
+        }
+
+        /**
+         * Calculates the max size of PIP.
+         *
+         * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
+         * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
+         * whole screen. A linear function is used to calculate these sizes.
+         *
+         * @param aspectRatio aspect ratio of the PIP window
+         * @return dimensions of the max size of the PIP
+         */
+        @Override
+        public Size getMaxSize(float aspectRatio) {
+            final int totalHorizontalPadding = getInsetBounds().left
+                    + (getDisplayBounds().width() - getInsetBounds().right);
+            final int totalVerticalPadding = getInsetBounds().top
+                    + (getDisplayBounds().height() - getInsetBounds().bottom);
+
+            final int shorterLength = (int) (1f * Math.min(
+                    getDisplayBounds().width() - totalHorizontalPadding,
+                    getDisplayBounds().height() - totalVerticalPadding));
+
+            int maxWidth, maxHeight;
+
+            // use the optimized max sizing logic only within a certain aspect ratio range
+            if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
+                // this formula and its derivation is explained in b/198643358#comment16
+                maxWidth = (int) (mOptimizedAspectRatio * shorterLength
+                        + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
+                        + aspectRatio));
+                maxHeight = (int) (maxWidth / aspectRatio);
+            } else {
+                if (aspectRatio > 1f) {
+                    maxWidth = shorterLength;
+                    maxHeight = (int) (maxWidth / aspectRatio);
+                } else {
+                    maxHeight = shorterLength;
+                    maxWidth = (int) (maxHeight * aspectRatio);
+                }
+            }
+
+            return new Size(maxWidth, maxHeight);
+        }
+
+        /**
+         * Decreases the dimensions by a percentage relative to max size to get default size.
+         *
+         * @param aspectRatio aspect ratio of the PIP window
+         * @return dimensions of the default size of the PIP
+         */
+        @Override
+        public Size getDefaultSize(float aspectRatio) {
+            Size minSize = this.getMinSize(aspectRatio);
+
+            if (mOverrideMinSize != null) {
+                return minSize;
+            }
+
+            Size maxSize = this.getMaxSize(aspectRatio);
+
+            int defaultWidth = Math.max((int) (maxSize.getWidth() * mDefaultSizePercent),
+                    minSize.getWidth());
+            int defaultHeight = Math.max((int) (maxSize.getHeight() * mDefaultSizePercent),
+                    minSize.getHeight());
+
+            return new Size(defaultWidth, defaultHeight);
+        }
+
+        /**
+         * Decreases the dimensions by a certain percentage relative to max size to get min size.
+         *
+         * @param aspectRatio aspect ratio of the PIP window
+         * @return dimensions of the min size of the PIP
+         */
+        @Override
+        public Size getMinSize(float aspectRatio) {
+            // if there is an overridden min size provided, return that
+            if (mOverrideMinSize != null) {
+                return adjustOverrideMinSizeToAspectRatio(aspectRatio);
+            }
+
+            Size maxSize = this.getMaxSize(aspectRatio);
+
+            int minWidth = (int) (maxSize.getWidth() * mMinimumSizePercent);
+            int minHeight = (int) (maxSize.getHeight() * mMinimumSizePercent);
+
+            // make sure the calculated min size is not smaller than the allowed default min size
+            if (aspectRatio > 1f) {
+                minHeight = (int) Math.max(minHeight, mDefaultMinSize);
+                minWidth = (int) (minHeight * aspectRatio);
+            } else {
+                minWidth = (int) Math.max(minWidth, mDefaultMinSize);
+                minHeight = (int) (minWidth / aspectRatio);
+            }
+            return new Size(minWidth, minHeight);
+        }
+
+        /**
+         * Returns the size for target aspect ratio making sure new size conforms with the rules.
+         *
+         * <p>Recalculates the dimensions such that the target aspect ratio is achieved, while
+         * maintaining the same maximum size to current size ratio.
+         *
+         * @param size current size
+         * @param aspectRatio target aspect ratio
+         */
+        @Override
+        public Size getSizeForAspectRatio(Size size, float aspectRatio) {
+            // getting the percentage of the max size that current size takes
+            float currAspectRatio = (float) size.getWidth() / size.getHeight();
+            Size currentMaxSize = getMaxSize(currAspectRatio);
+            float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
+
+            // getting the max size for the target aspect ratio
+            Size updatedMaxSize = getMaxSize(aspectRatio);
+
+            int width = (int) (updatedMaxSize.getWidth() * currentPercent);
+            int height = (int) (updatedMaxSize.getHeight() * currentPercent);
+
+            // adjust the dimensions if below allowed min edge size
+            if (width < getMinEdgeSize() && aspectRatio <= 1) {
+                width = getMinEdgeSize();
+                height = (int) (width / aspectRatio);
+            } else if (height < getMinEdgeSize() && aspectRatio > 1) {
+                height = getMinEdgeSize();
+                width = (int) (height * aspectRatio);
+            }
+
+            // reduce the dimensions of the updated size to the calculated percentage
+            return new Size(width, height);
+        }
+    }
+
+    private class SizeSpecDefaultImpl implements SizeSpecSource {
+        private float mDefaultSizePercent;
+        private float mMinimumSizePercent;
+
+        @Override
+        public void reloadResources() {
+            final Resources res = mContext.getResources();
+
+            mMaxAspectRatioForMinSize = res.getFloat(
+                    R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
+            mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
+
+            mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent);
+            mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
+        }
+
+        @Override
+        public Size getMaxSize(float aspectRatio) {
+            final int shorterLength = Math.min(getDisplayBounds().width(),
+                    getDisplayBounds().height());
+
+            final int totalHorizontalPadding = getInsetBounds().left
+                    + (getDisplayBounds().width() - getInsetBounds().right);
+            final int totalVerticalPadding = getInsetBounds().top
+                    + (getDisplayBounds().height() - getInsetBounds().bottom);
+
+            final int maxWidth, maxHeight;
+
+            if (aspectRatio > 1f) {
+                maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(),
+                        shorterLength - totalHorizontalPadding);
+                maxHeight = (int) (maxWidth / aspectRatio);
+            } else {
+                maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(),
+                        shorterLength - totalVerticalPadding);
+                maxWidth = (int) (maxHeight * aspectRatio);
+            }
+
+            return new Size(maxWidth, maxHeight);
+        }
+
+        @Override
+        public Size getDefaultSize(float aspectRatio) {
+            if (mOverrideMinSize != null) {
+                return this.getMinSize(aspectRatio);
+            }
+
+            final int smallestDisplaySize = Math.min(getDisplayBounds().width(),
+                    getDisplayBounds().height());
+            final int minSize = (int) Math.max(getMinEdgeSize(),
+                    smallestDisplaySize * mDefaultSizePercent);
+
+            final int width;
+            final int height;
+
+            if (aspectRatio <= mMinAspectRatioForMinSize
+                    || aspectRatio > mMaxAspectRatioForMinSize) {
+                // Beyond these points, we can just use the min size as the shorter edge
+                if (aspectRatio <= 1) {
+                    // Portrait, width is the minimum size
+                    width = minSize;
+                    height = Math.round(width / aspectRatio);
+                } else {
+                    // Landscape, height is the minimum size
+                    height = minSize;
+                    width = Math.round(height * aspectRatio);
+                }
+            } else {
+                // Within these points, ensure that the bounds fit within the radius of the limits
+                // at the points
+                final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
+                final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
+                height = (int) Math.round(Math.sqrt((radius * radius)
+                        / (aspectRatio * aspectRatio + 1)));
+                width = Math.round(height * aspectRatio);
+            }
+
+            return new Size(width, height);
+        }
+
+        @Override
+        public Size getMinSize(float aspectRatio) {
+            if (mOverrideMinSize != null) {
+                return adjustOverrideMinSizeToAspectRatio(aspectRatio);
+            }
+
+            final int shorterLength = Math.min(getDisplayBounds().width(),
+                    getDisplayBounds().height());
+            final int minWidth, minHeight;
+
+            if (aspectRatio > 1f) {
+                minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(),
+                        shorterLength * mMinimumSizePercent);
+                minHeight = (int) (minWidth / aspectRatio);
+            } else {
+                minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(),
+                        shorterLength * mMinimumSizePercent);
+                minWidth = (int) (minHeight * aspectRatio);
+            }
+
+            return new Size(minWidth, minHeight);
+        }
+
+        @Override
+        public Size getSizeForAspectRatio(Size size, float aspectRatio) {
+            final int smallestSize = Math.min(size.getWidth(), size.getHeight());
+            final int minSize = Math.max(getMinEdgeSize(), smallestSize);
+
+            final int width;
+            final int height;
+            if (aspectRatio <= 1) {
+                // Portrait, width is the minimum size.
+                width = minSize;
+                height = Math.round(width / aspectRatio);
+            } else {
+                // Landscape, height is the minimum size
+                height = minSize;
+                width = Math.round(height * aspectRatio);
+            }
+
+            return new Size(width, height);
+        }
+    }
+
+    public PipSizeSpecHandler(Context context) {
+        mContext = context;
+
+        boolean enablePipSizeLargeScreen = SystemProperties
+                .getBoolean("persist.wm.debug.enable_pip_size_large_screen", false);
+
+        // choose between two implementations of size spec logic
+        if (enablePipSizeLargeScreen) {
+            mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
+        } else {
+            mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
+        }
+
+        reloadResources();
+    }
+
+    /** Reloads the resources */
+    public void onConfigurationChanged() {
+        reloadResources();
+    }
+
+    private void reloadResources() {
+        final Resources res = mContext.getResources();
+
+        mDefaultMinSize = res.getDimensionPixelSize(
+                R.dimen.default_minimal_size_pip_resizable_task);
+        mOverridableMinSize = res.getDimensionPixelSize(
+                R.dimen.overridable_minimal_size_pip_resizable_task);
+
+        final String screenEdgeInsetsDpString = res.getString(
+                R.string.config_defaultPictureInPictureScreenEdgeInsets);
+        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+                ? Size.parseSize(screenEdgeInsetsDpString)
+                : null;
+        mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+                : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
+                        dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
+
+        // update the internal resources of the size spec source's stub
+        mSizeSpecSourceImpl.reloadResources();
+    }
+
+    /** Returns the display's bounds. */
+    @NonNull
+    public Rect getDisplayBounds() {
+        return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+    }
+
+    /** Get the display layout. */
+    @NonNull
+    public DisplayLayout getDisplayLayout() {
+        return mDisplayLayout;
+    }
+
+    /** Update the display layout. */
+    public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
+        mDisplayLayout.set(displayLayout);
+    }
+
+    public Point getScreenEdgeInsets() {
+        return mScreenEdgeInsets;
+    }
+
+    /**
+     * Returns the inset bounds the PIP window can be visible in.
+     */
+    public Rect getInsetBounds() {
+        Rect insetBounds = new Rect();
+        final DisplayLayout displayLayout = getDisplayLayout();
+        Rect insets = getDisplayLayout().stableInsets();
+        insetBounds.set(insets.left + mScreenEdgeInsets.x,
+                insets.top + mScreenEdgeInsets.y,
+                displayLayout.width() - insets.right - mScreenEdgeInsets.x,
+                displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+        return insetBounds;
+    }
+
+    /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
+    public void setOverrideMinSize(@Nullable Size overrideMinSize) {
+        mOverrideMinSize = overrideMinSize;
+    }
+
+    /** Returns the preferred minimal size specified by the activity in PIP. */
+    @Nullable
+    public Size getOverrideMinSize() {
+        if (mOverrideMinSize != null
+                && (mOverrideMinSize.getWidth() < mOverridableMinSize
+                || mOverrideMinSize.getHeight() < mOverridableMinSize)) {
+            return new Size(mOverridableMinSize, mOverridableMinSize);
+        }
+
+        return mOverrideMinSize;
+    }
+
+    /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
+    public int getOverrideMinEdgeSize() {
+        if (mOverrideMinSize == null) return 0;
+        return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
+    }
+
+    public int getMinEdgeSize() {
+        return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize();
+    }
+
+    /**
+     * Returns the size for the max size spec.
+     */
+    public Size getMaxSize(float aspectRatio) {
+        return mSizeSpecSourceImpl.getMaxSize(aspectRatio);
+    }
+
+    /**
+     * Returns the size for the default size spec.
+     */
+    public Size getDefaultSize(float aspectRatio) {
+        return mSizeSpecSourceImpl.getDefaultSize(aspectRatio);
+    }
+
+    /**
+     * Returns the size for the min size spec.
+     */
+    public Size getMinSize(float aspectRatio) {
+        return mSizeSpecSourceImpl.getMinSize(aspectRatio);
+    }
+
+    /**
+     * Returns the adjusted size so that it conforms to the given aspectRatio.
+     *
+     * @param size current size
+     * @param aspectRatio target aspect ratio
+     */
+    public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) {
+        if (size.equals(mOverrideMinSize)) {
+            return adjustOverrideMinSizeToAspectRatio(aspectRatio);
+        }
+
+        return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio);
+    }
+
+    /**
+     * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+     *
+     * <p>Overridden min size needs to be adjusted in its own way while making sure that the target
+     * aspect ratio is maintained
+     *
+     * @param aspectRatio target aspect ratio
+     */
+    @Nullable
+    @VisibleForTesting
+    Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) {
+        if (mOverrideMinSize == null) {
+            return null;
+        }
+        final Size size = getOverrideMinSize();
+        final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
+        if (sizeAspectRatio > aspectRatio) {
+            // Size is wider, fix the width and increase the height
+            return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
+        } else {
+            // Size is taller, fix the height and adjust the width.
+            return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
+        }
+    }
+
+    /** Dumps internal state. */
+    public void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + TAG);
+        pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl.toString());
+        pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
+        pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 83bc7c0..0e8d13d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -78,7 +78,8 @@
     private boolean mEnableResize;
     private final Context mContext;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
-    private final @NonNull PipBoundsState mPipBoundsState;
+    @NonNull private final PipBoundsState mPipBoundsState;
+    @NonNull private final PipSizeSpecHandler mPipSizeSpecHandler;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipDismissTargetHandler mPipDismissTargetHandler;
     private final PipTaskOrganizer mPipTaskOrganizer;
@@ -99,7 +100,6 @@
 
     // The reference inset bounds, used to determine the dismiss fraction
     private final Rect mInsetBounds = new Rect();
-    private int mExpandedShortestEdgeSize;
 
     // Used to workaround an issue where the WM rotation happens before we are notified, allowing
     // us to send stale bounds
@@ -120,7 +120,6 @@
     private float mSavedSnapFraction = -1f;
     private boolean mSendingHoverAccessibilityEvents;
     private boolean mMovementWithinDismiss;
-    private float mMinimumSizePercent;
 
     // Touch state
     private final PipTouchState mTouchState;
@@ -174,6 +173,7 @@
             PhonePipMenuController menuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
+            @NonNull PipSizeSpecHandler pipSizeSpecHandler,
             PipTaskOrganizer pipTaskOrganizer,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -184,6 +184,7 @@
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
+        mPipSizeSpecHandler = pipSizeSpecHandler;
         mPipTaskOrganizer = pipTaskOrganizer;
         mMenuController = menuController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -271,10 +272,7 @@
     private void reloadResources() {
         final Resources res = mContext.getResources();
         mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
-        mExpandedShortestEdgeSize = res.getDimensionPixelSize(
-                R.dimen.pip_expanded_shortest_edge_size);
         mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
-        mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
         mPipDismissTargetHandler.updateMagneticTargetSize();
     }
 
@@ -337,8 +335,10 @@
         mMotionHelper.synchronizePinnedStackBounds();
         reloadResources();
 
-        // Recreate the dismiss target for the new orientation.
-        mPipDismissTargetHandler.createOrUpdateDismissTarget();
+        if (mPipTaskOrganizer.isInPip()) {
+            // Recreate the dismiss target for the new orientation.
+            mPipDismissTargetHandler.createOrUpdateDismissTarget();
+        }
     }
 
     public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -405,10 +405,7 @@
 
         // Calculate the expanded size
         float aspectRatio = (float) normalBounds.width() / normalBounds.height();
-        Point displaySize = new Point();
-        mContext.getDisplay().getRealSize(displaySize);
-        Size expandedSize = mPipBoundsAlgorithm.getSizeForAspectRatio(
-                aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
+        Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
         mPipBoundsState.setExpandedBounds(
                 new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
         Rect expandedMovementBounds = new Rect();
@@ -416,7 +413,7 @@
                 mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
                 bottomOffset);
 
-        updatePipSizeConstraints(insetBounds, normalBounds, aspectRatio);
+        updatePipSizeConstraints(normalBounds, aspectRatio);
 
         // The extra offset does not really affect the movement bounds, but are applied based on the
         // current state (ime showing, or shelf offset) when we need to actually shift
@@ -494,14 +491,14 @@
      * @param aspectRatio aspect ratio to use for the calculation of min/max size
      */
     public void updateMinMaxSize(float aspectRatio) {
-        updatePipSizeConstraints(mInsetBounds, mPipBoundsState.getNormalBounds(),
+        updatePipSizeConstraints(mPipBoundsState.getNormalBounds(),
                 aspectRatio);
     }
 
-    private void updatePipSizeConstraints(Rect insetBounds, Rect normalBounds,
+    private void updatePipSizeConstraints(Rect normalBounds,
             float aspectRatio) {
         if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
-            updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio);
+            updatePinchResizeSizeConstraints(aspectRatio);
         } else {
             mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
             mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
@@ -509,26 +506,13 @@
         }
     }
 
-    private void updatePinchResizeSizeConstraints(Rect insetBounds, Rect normalBounds,
-            float aspectRatio) {
-        final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
-                mPipBoundsState.getDisplayBounds().height());
-        final int totalHorizontalPadding = insetBounds.left
-                + (mPipBoundsState.getDisplayBounds().width() - insetBounds.right);
-        final int totalVerticalPadding = insetBounds.top
-                + (mPipBoundsState.getDisplayBounds().height() - insetBounds.bottom);
+    private void updatePinchResizeSizeConstraints(float aspectRatio) {
         final int minWidth, minHeight, maxWidth, maxHeight;
-        if (aspectRatio > 1f) {
-            minWidth = (int) Math.min(normalBounds.width(), shorterLength * mMinimumSizePercent);
-            minHeight = (int) (minWidth / aspectRatio);
-            maxWidth = (int) Math.max(normalBounds.width(), shorterLength - totalHorizontalPadding);
-            maxHeight = (int) (maxWidth / aspectRatio);
-        } else {
-            minHeight = (int) Math.min(normalBounds.height(), shorterLength * mMinimumSizePercent);
-            minWidth = (int) (minHeight * aspectRatio);
-            maxHeight = (int) Math.max(normalBounds.height(), shorterLength - totalVerticalPadding);
-            maxWidth = (int) (maxHeight * aspectRatio);
-        }
+
+        minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth();
+        minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight();
+        maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth();
+        maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight();
 
         mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
         mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
@@ -1062,11 +1046,6 @@
         mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
                 mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
         mMotionHelper.onMovementBoundsChanged();
-
-        boolean isMenuExpanded = mMenuState == MENU_STATE_FULL;
-        mPipBoundsState.setMinEdgeSize(
-                isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize
-                        : mPipBoundsAlgorithm.getDefaultMinSize());
     }
 
     private Rect getMovementBounds(Rect curBounds) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 1ff77f7..22feb43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -41,6 +41,7 @@
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
@@ -63,9 +64,10 @@
 
     public TvPipBoundsAlgorithm(Context context,
             @NonNull TvPipBoundsState tvPipBoundsState,
-            @NonNull PipSnapAlgorithm pipSnapAlgorithm) {
+            @NonNull PipSnapAlgorithm pipSnapAlgorithm,
+            @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
         super(context, tvPipBoundsState, pipSnapAlgorithm,
-                new PipKeepClearAlgorithmInterface() {});
+                new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler);
         this.mTvPipBoundsState = tvPipBoundsState;
         this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
         reloadResources(context);
@@ -370,7 +372,8 @@
             if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
-                int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y)
+                int maxHeight = displayLayout.height()
+                        - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y)
                         - pipDecorations.top - pipDecorations.bottom;
                 float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
 
@@ -393,7 +396,8 @@
             if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
-                int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x)
+                int maxWidth = displayLayout.width()
+                        - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x)
                         - pipDecorations.left - pipDecorations.right;
                 float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
                 if (maxWidth > aspectRatioWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index ca22882..4e3ee51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -30,6 +30,7 @@
 
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -64,8 +65,9 @@
     private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
     private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
 
-    public TvPipBoundsState(@NonNull Context context) {
-        super(context);
+    public TvPipBoundsState(@NonNull Context context,
+            @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+        super(context, pipSizeSpecHandler);
         mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 4e1b046..6bc666f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -50,6 +50,7 @@
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.ShellController;
@@ -102,6 +103,7 @@
 
     private final ShellController mShellController;
     private final TvPipBoundsState mTvPipBoundsState;
+    private final PipSizeSpecHandler mPipSizeSpecHandler;
     private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
     private final TvPipBoundsController mTvPipBoundsController;
     private final PipAppOpsListener mAppOpsListener;
@@ -133,6 +135,7 @@
             ShellInit shellInit,
             ShellController shellController,
             TvPipBoundsState tvPipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             TvPipBoundsController tvPipBoundsController,
             PipAppOpsListener pipAppOpsListener,
@@ -151,6 +154,7 @@
                 shellInit,
                 shellController,
                 tvPipBoundsState,
+                pipSizeSpecHandler,
                 tvPipBoundsAlgorithm,
                 tvPipBoundsController,
                 pipAppOpsListener,
@@ -171,6 +175,7 @@
             ShellInit shellInit,
             ShellController shellController,
             TvPipBoundsState tvPipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             TvPipBoundsController tvPipBoundsController,
             PipAppOpsListener pipAppOpsListener,
@@ -189,9 +194,13 @@
         mShellController = shellController;
         mDisplayController = displayController;
 
+        DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
+
         mTvPipBoundsState = tvPipBoundsState;
+        mTvPipBoundsState.setDisplayLayout(layout);
         mTvPipBoundsState.setDisplayId(context.getDisplayId());
-        mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
+        mPipSizeSpecHandler = pipSizeSpecHandler;
+        mPipSizeSpecHandler.setDisplayLayout(layout);
         mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
         mTvPipBoundsController = tvPipBoundsController;
         mTvPipBoundsController.setListener(this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 38099fc..21eeaa2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -535,17 +536,11 @@
                 fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else {
-                try {
-                    adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
-                    ActivityTaskManager.getService().startActivityFromRecents(taskId, options2);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error starting remote animation", e);
-                }
+                taskId = INVALID_TASK_ID;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
-                return;
             }
         }
         mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
@@ -586,17 +581,11 @@
                 fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else {
-                try {
-                    adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
-                    pendingIntent1.send();
-                } catch (RemoteException | PendingIntent.CanceledException e) {
-                    Slog.e(TAG, "Error starting remote animation", e);
-                }
+                pendingIntent2 = null;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
                         "Cancel entering split as not supporting multi-instances");
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
-                return;
             }
         }
         mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index a6c4ac2..39cf5f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -629,9 +629,19 @@
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (options1 == null) options1 = new Bundle();
+        if (pendingIntent2 == null) {
+            // Launching a solo task.
+            ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+            activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
+            options1 = activityOptions.toBundle();
+            addActivityOptions(options1, null /* launchTarget */);
+            wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+            mSyncQueue.queue(wct);
+            return;
+        }
+
         addActivityOptions(options1, mSideStage);
         wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
-
         startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
                 splitRatio, adapter, instanceId);
     }
@@ -666,9 +676,19 @@
             InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (options1 == null) options1 = new Bundle();
+        if (taskId == INVALID_TASK_ID) {
+            // Launching a solo task.
+            ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+            activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
+            options1 = activityOptions.toBundle();
+            addActivityOptions(options1, null /* launchTarget */);
+            wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+            mSyncQueue.queue(wct);
+            return;
+        }
+
         addActivityOptions(options1, mSideStage);
         wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
-
         startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
                 instanceId);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 053491e..22e8045 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -430,7 +430,8 @@
         }
 
         @Override
-        public @Nullable SplashScreenView get() {
+        @Nullable
+        public SplashScreenView get() {
             synchronized (this) {
                 while (!mIsViewSet) {
                     try {
@@ -691,7 +692,7 @@
         private final TaskSnapshotWindow mTaskSnapshotWindow;
         private SplashScreenView mContentView;
         private boolean mSetSplashScreen;
-        private @StartingWindowType int mSuggestType;
+        @StartingWindowType private int mSuggestType;
         private int mBGColor;
         private final long mCreateTime;
         private int mSystemBarAppearance;
@@ -732,7 +733,7 @@
 
         // Reset the system bar color which set by splash screen, make it align to the app.
         private void clearSystemBarColor() {
-            if (mDecorView == null) {
+            if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
                 return;
             }
             if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 129924a..2981f5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -36,6 +36,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
+import com.android.wm.shell.transition.Transitions;
 
 /**
  * View model for the window decoration with a caption and shadows. Works with
@@ -65,6 +66,9 @@
         mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
         mSyncQueue = syncQueue;
+        if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
+        }
     }
 
     @Override
@@ -143,8 +147,8 @@
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
                 || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
-                    && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode()
-                        == WINDOWING_MODE_FREEFORM);
+                && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode()
+                == WINDOWING_MODE_FREEFORM);
     }
 
     private void createWindowDecoration(
@@ -174,26 +178,29 @@
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
-        windowDecoration.setDragResizeCallback(taskPositioner);
+        windowDecoration.setDragPositioningCallback(taskPositioner);
+        windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT);
         setupCaptionColor(taskInfo, windowDecoration);
     }
 
     private class CaptionTouchEventListener implements
-            View.OnClickListener, View.OnTouchListener {
+            View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
 
         private final int mTaskId;
         private final WindowContainerToken mTaskToken;
-        private final DragResizeCallback mDragResizeCallback;
+        private final DragPositioningCallback mDragPositioningCallback;
+        private final DragDetector mDragDetector;
 
         private int mDragPointerId = -1;
 
         private CaptionTouchEventListener(
                 RunningTaskInfo taskInfo,
-                DragResizeCallback dragResizeCallback) {
+                DragPositioningCallback dragPositioningCallback) {
             mTaskId = taskInfo.taskId;
             mTaskToken = taskInfo.token;
-            mDragResizeCallback = dragResizeCallback;
+            mDragPositioningCallback = dragPositioningCallback;
+            mDragDetector = new DragDetector(this);
         }
 
         @Override
@@ -216,7 +223,7 @@
             if (v.getId() != R.id.caption) {
                 return false;
             }
-            handleEventForMove(e);
+            mDragDetector.onMotionEvent(e);
 
             if (e.getAction() != MotionEvent.ACTION_DOWN) {
                 return false;
@@ -235,32 +242,34 @@
          * @param e {@link MotionEvent} to process
          * @return {@code true} if a drag is happening; or {@code false} if it is not
          */
-        private void handleEventForMove(MotionEvent e) {
+        @Override
+        public boolean handleMotionEvent(MotionEvent e) {
             final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-                return;
+                return false;
             }
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     mDragPointerId = e.getPointerId(0);
-                    mDragResizeCallback.onDragResizeStart(
+                    mDragPositioningCallback.onDragPositioningStart(
                             0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
                     break;
                 }
                 case MotionEvent.ACTION_MOVE: {
                     int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    mDragResizeCallback.onDragResizeMove(
+                    mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     break;
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
                     int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    mDragResizeCallback.onDragResizeEnd(
+                    mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     break;
                 }
             }
+            return true;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index d26f1fc..060dc4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -47,9 +47,9 @@
 
     private View.OnClickListener mOnCaptionButtonClickListener;
     private View.OnTouchListener mOnCaptionTouchListener;
-    private DragResizeCallback mDragResizeCallback;
+    private DragPositioningCallback mDragPositioningCallback;
     private DragResizeInputListener mDragResizeListener;
-    private final DragDetector mDragDetector;
+    private DragDetector mDragDetector;
 
     private RelayoutParams mRelayoutParams = new RelayoutParams();
     private final RelayoutResult<WindowDecorLinearLayout> mResult =
@@ -69,7 +69,6 @@
         mHandler = handler;
         mChoreographer = choreographer;
         mSyncQueue = syncQueue;
-        mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
     }
 
     void setCaptionListeners(
@@ -79,8 +78,13 @@
         mOnCaptionTouchListener = onCaptionTouchListener;
     }
 
-    void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
-        mDragResizeCallback = dragResizeCallback;
+    void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
+        mDragPositioningCallback = dragPositioningCallback;
+    }
+
+    void setDragDetector(DragDetector dragDetector) {
+        mDragDetector = dragDetector;
+        mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
     }
 
     @Override
@@ -147,7 +151,7 @@
                     mChoreographer,
                     mDisplay.getDisplayId(),
                     mDecorationContainerSurface,
-                    mDragResizeCallback);
+                    mDragPositioningCallback);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2863adc..606cf28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -205,34 +205,29 @@
     }
 
     private class DesktopModeTouchEventListener implements
-            View.OnClickListener, View.OnTouchListener {
+            View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
 
         private final int mTaskId;
         private final WindowContainerToken mTaskToken;
-        private final DragResizeCallback mDragResizeCallback;
+        private final DragPositioningCallback mDragPositioningCallback;
         private final DragDetector mDragDetector;
 
         private int mDragPointerId = -1;
 
         private DesktopModeTouchEventListener(
                 RunningTaskInfo taskInfo,
-                DragResizeCallback dragResizeCallback,
-                DragDetector dragDetector) {
+                DragPositioningCallback dragPositioningCallback) {
             mTaskId = taskInfo.taskId;
             mTaskToken = taskInfo.token;
-            mDragResizeCallback = dragResizeCallback;
-            mDragDetector = dragDetector;
+            mDragPositioningCallback = dragPositioningCallback;
+            mDragDetector = new DragDetector(this);
         }
 
         @Override
         public void onClick(View v) {
             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             final int id = v.getId();
-            if (id == R.id.close_window) {
-                mTaskOperations.closeTask(mTaskToken);
-            } else if (id == R.id.back_button) {
-                mTaskOperations.injectBackKey();
-            } else if (id == R.id.caption_handle) {
+            if (id == R.id.caption_handle) {
                 decoration.createHandleMenu();
             } else if (id == R.id.desktop_button) {
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
@@ -254,8 +249,7 @@
                 return false;
             }
             if (id == R.id.caption_handle) {
-                isDrag = mDragDetector.detectDragEvent(e);
-                handleEventForMove(e);
+                isDrag = mDragDetector.onMotionEvent(e);
             }
             if (e.getAction() != MotionEvent.ACTION_DOWN) {
                 return isDrag;
@@ -272,30 +266,30 @@
 
         /**
          * @param e {@link MotionEvent} to process
-         * @return {@code true} if a drag is happening; or {@code false} if it is not
+         * @return {@code true} if the motion event is handled.
          */
-        private void handleEventForMove(MotionEvent e) {
+        @Override
+        public boolean handleMotionEvent(MotionEvent e) {
             final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
             if (DesktopModeStatus.isProto2Enabled()
                     && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
-                return;
+                return false;
             }
             if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
-                    && mDesktopModeController.get().getDisplayAreaWindowingMode(
-                    taskInfo.displayId)
+                    && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
                     == WINDOWING_MODE_FULLSCREEN) {
-                return;
+                return false;
             }
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     mDragPointerId = e.getPointerId(0);
-                    mDragResizeCallback.onDragResizeStart(
+                    mDragPositioningCallback.onDragPositioningStart(
                             0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
                     break;
                 }
                 case MotionEvent.ACTION_MOVE: {
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
-                    mDragResizeCallback.onDragResizeMove(
+                    mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     break;
                 }
@@ -304,7 +298,7 @@
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     final int statusBarHeight = mDisplayController
                             .getDisplayLayout(taskInfo.displayId).stableInsets().top;
-                    mDragResizeCallback.onDragResizeEnd(
+                    mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
                         if (DesktopModeStatus.isProto2Enabled()) {
@@ -324,6 +318,7 @@
                     break;
                 }
             }
+            return true;
         }
     }
 
@@ -560,10 +555,10 @@
         final TaskPositioner taskPositioner =
                 new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
         final DesktopModeTouchEventListener touchEventListener =
-                new DesktopModeTouchEventListener(
-                        taskInfo, taskPositioner, windowDecoration.getDragDetector());
+                new DesktopModeTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
-        windowDecoration.setDragResizeCallback(taskPositioner);
+        windowDecoration.setDragPositioningCallback(taskPositioner);
+        windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT);
         incrementEventReceiverTasks(taskInfo.displayId);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 1a38d24..744c18f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -25,7 +25,6 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.PointF;
-import android.graphics.Rect;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
 import android.view.Choreographer;
@@ -55,11 +54,12 @@
 
     private View.OnClickListener mOnCaptionButtonClickListener;
     private View.OnTouchListener mOnCaptionTouchListener;
-    private DragResizeCallback mDragResizeCallback;
+    private DragPositioningCallback mDragPositioningCallback;
     private DragResizeInputListener mDragResizeListener;
-    private final DragDetector mDragDetector;
+    private DragDetector mDragDetector;
 
     private RelayoutParams mRelayoutParams = new RelayoutParams();
+    private final int mCaptionMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
     private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
             new WindowDecoration.RelayoutResult<>();
 
@@ -81,7 +81,6 @@
         mChoreographer = choreographer;
         mSyncQueue = syncQueue;
         mDesktopActive = DesktopModeStatus.isActive(mContext);
-        mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
     }
 
     void setCaptionListeners(
@@ -91,12 +90,13 @@
         mOnCaptionTouchListener = onCaptionTouchListener;
     }
 
-    void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
-        mDragResizeCallback = dragResizeCallback;
+    void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
+        mDragPositioningCallback = dragPositioningCallback;
     }
 
-    DragDetector getDragDetector() {
-        return mDragDetector;
+    void setDragDetector(DragDetector dragDetector) {
+        mDragDetector = dragDetector;
+        mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
     }
 
     @Override
@@ -131,22 +131,10 @@
         mRelayoutParams.mRunningTaskInfo = taskInfo;
         mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
         mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
-        mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
         if (isDragResizeable) {
             mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
         }
-        final Resources resources = mDecorWindowContext.getResources();
-        final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
-        final int captionHeight = loadDimensionPixelSize(resources,
-                mRelayoutParams.mCaptionHeightId);
-        final int captionWidth = loadDimensionPixelSize(resources,
-                mRelayoutParams.mCaptionWidthId);
-        final int captionLeft = taskBounds.width() / 2
-                - captionWidth / 2;
-        final int captionTop = taskBounds.top
-                <= captionHeight / 2 ? 0 : -captionHeight / 2;
-        mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -191,7 +179,7 @@
                     mChoreographer,
                     mDisplay.getDisplayId(),
                     mDecorationContainerSurface,
-                    mDragResizeCallback);
+                    mDragPositioningCallback);
         }
 
         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
@@ -212,10 +200,6 @@
     private void setupRootView() {
         final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
         caption.setOnTouchListener(mOnCaptionTouchListener);
-        final View close = caption.findViewById(R.id.close_window);
-        close.setOnClickListener(mOnCaptionButtonClickListener);
-        final View back = caption.findViewById(R.id.back_button);
-        back.setOnClickListener(mOnCaptionButtonClickListener);
         final View handle = caption.findViewById(R.id.caption_handle);
         handle.setOnTouchListener(mOnCaptionTouchListener);
         handle.setOnClickListener(mOnCaptionButtonClickListener);
@@ -230,8 +214,6 @@
         desktop.setOnClickListener(mOnCaptionButtonClickListener);
         final View split = menu.findViewById(R.id.split_screen_button);
         split.setOnClickListener(mOnCaptionButtonClickListener);
-        final View more = menu.findViewById(R.id.more_button);
-        more.setOnClickListener(mOnCaptionButtonClickListener);
     }
 
     /**
@@ -264,10 +246,6 @@
     void setButtonVisibility(boolean visible) {
         final int visibility = visible ? View.VISIBLE : View.GONE;
         final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
-        final View back = caption.findViewById(R.id.back_button);
-        final View close = caption.findViewById(R.id.close_window);
-        back.setVisibility(visibility);
-        close.setVisibility(visibility);
         final int buttonTintColorRes =
                 mDesktopActive ? R.color.decor_button_dark_color
                         : R.color.decor_button_light_color;
@@ -297,14 +275,18 @@
     void createHandleMenu() {
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         final Resources resources = mDecorWindowContext.getResources();
-        final int x = mRelayoutParams.mCaptionX;
-        final int y = mRelayoutParams.mCaptionY;
-        final int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
-        final int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+        final int captionWidth = mTaskInfo.getConfiguration()
+                .windowConfiguration.getBounds().width();
+        final int menuWidth = loadDimensionPixelSize(
+                resources, mCaptionMenuWidthId);
+        final int height = loadDimensionPixelSize(
+                resources, mRelayoutParams.mCaptionHeightId);
+        final int x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
+                - mResult.mDecorContainerOffsetX;
+        final int y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
         String namePrefix = "Caption Menu";
-        mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t,
-                x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
-                width, height);
+        mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
+                menuWidth, height);
         mSyncQueue.runInSync(transaction -> {
             transaction.merge(t);
             t.close();
@@ -370,7 +352,7 @@
         if (mResult.mRootView == null) return false;
         final PointF inputPoint = offsetCaptionLocation(ev);
         final View view = mResult.mRootView.findViewById(layoutId);
-        return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
+        return view != null && pointInView(view, inputPoint.x, inputPoint.y);
     }
 
     boolean checkTouchEventInHandle(MotionEvent ev) {
@@ -387,32 +369,39 @@
      */
     void checkClickEvent(MotionEvent ev) {
         if (mResult.mRootView == null) return;
-        final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
-        final PointF inputPoint = offsetCaptionLocation(ev);
         if (!isHandleMenuActive()) {
+            final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
             final View handle = caption.findViewById(R.id.caption_handle);
-            clickIfPointInView(inputPoint, handle);
+            clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
         } else {
             final View menu = mHandleMenu.mWindowViewHost.getView();
+            final int captionWidth = mTaskInfo.getConfiguration().windowConfiguration
+                    .getBounds().width();
+            final int menuX = mRelayoutParams.mCaptionX + (captionWidth / 2)
+                    - (menu.getWidth() / 2);
+            final PointF inputPoint = new PointF(ev.getX() - menuX, ev.getY());
             final View fullscreen = menu.findViewById(R.id.fullscreen_button);
             if (clickIfPointInView(inputPoint, fullscreen)) return;
             final View desktop = menu.findViewById(R.id.desktop_button);
             if (clickIfPointInView(inputPoint, desktop)) return;
             final View split = menu.findViewById(R.id.split_screen_button);
             if (clickIfPointInView(inputPoint, split)) return;
-            final View more = menu.findViewById(R.id.more_button);
-            clickIfPointInView(inputPoint, more);
         }
     }
 
     private boolean clickIfPointInView(PointF inputPoint, View v) {
-        if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) {
+        if (pointInView(v, inputPoint.x, inputPoint.y)) {
             mOnCaptionButtonClickListener.onClick(v);
             return true;
         }
         return false;
     }
 
+    private boolean pointInView(View v, float x, float y) {
+        return v != null && v.getLeft() <= x && v.getRight() >= x
+                && v.getTop() <= y && v.getBottom() >= y;
+    }
+
     @Override
     public void close() {
         closeDragResizeListener();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index 0abe8ab..4fac843 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.windowdecor;
 
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
@@ -25,63 +26,82 @@
 import android.view.MotionEvent;
 
 /**
- * A detector for touch inputs that differentiates between drag and click inputs.
+ * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
+ * of {@link MotionEvent} and generates a new flow of motion events with slop in consideration to
+ * the event handler. In particular, it always passes down, up and cancel events. It'll pass move
+ * events only when there is at least one move event that's beyond the slop threshold. For the
+ * purpose of convenience it also passes all events of other actions.
+ *
  * All touch events must be passed through this class to track a drag event.
  */
-public class DragDetector {
+class DragDetector {
+    private final MotionEventHandler mEventHandler;
+
+    private final PointF mInputDownPoint = new PointF();
     private int mTouchSlop;
-    private PointF mInputDownPoint;
     private boolean mIsDragEvent;
     private int mDragPointerId;
-    public DragDetector(int touchSlop) {
-        mTouchSlop = touchSlop;
-        mInputDownPoint = new PointF();
-        mIsDragEvent = false;
-        mDragPointerId = -1;
+
+    private boolean mResultOfDownAction;
+
+    DragDetector(MotionEventHandler eventHandler) {
+        resetState();
+        mEventHandler = eventHandler;
     }
 
     /**
-     * Determine if {@link MotionEvent} is part of a drag event.
-     * @return {@code true} if this is a drag event, {@code false} if not
-     */
-    public boolean detectDragEvent(MotionEvent ev) {
-        switch (ev.getAction()) {
+     * The receiver of the {@link MotionEvent} flow.
+     *
+     * @return the result returned by {@link #mEventHandler}, or the result when
+     * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
+    */
+    boolean onMotionEvent(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
             case ACTION_DOWN: {
+                // Only touch screens generate noisy moves.
+                mIsDragEvent = (ev.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN;
                 mDragPointerId = ev.getPointerId(0);
                 float rawX = ev.getRawX(0);
                 float rawY = ev.getRawY(0);
                 mInputDownPoint.set(rawX, rawY);
-                return false;
+                mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
+                return mResultOfDownAction;
             }
             case ACTION_MOVE: {
                 if (!mIsDragEvent) {
                     int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
                     float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
                     float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
-                    if (Math.hypot(dx, dy) > mTouchSlop) {
-                        mIsDragEvent = true;
-                    }
+                    mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop;
                 }
-                return mIsDragEvent;
+                if (mIsDragEvent) {
+                    return mEventHandler.handleMotionEvent(ev);
+                } else {
+                    return mResultOfDownAction;
+                }
             }
-            case ACTION_UP: {
-                boolean result = mIsDragEvent;
-                mIsDragEvent = false;
-                mInputDownPoint.set(0, 0);
-                mDragPointerId = -1;
-                return result;
-            }
+            case ACTION_UP:
             case ACTION_CANCEL: {
-                mIsDragEvent = false;
-                mInputDownPoint.set(0, 0);
-                mDragPointerId = -1;
-                return false;
+                resetState();
+                return mEventHandler.handleMotionEvent(ev);
             }
+            default:
+                return mEventHandler.handleMotionEvent(ev);
         }
-        return mIsDragEvent;
     }
 
-    public void setTouchSlop(int touchSlop) {
+    void setTouchSlop(int touchSlop) {
         mTouchSlop = touchSlop;
     }
+
+    private void resetState() {
+        mIsDragEvent = false;
+        mInputDownPoint.set(0, 0);
+        mDragPointerId = -1;
+        mResultOfDownAction = false;
+    }
+
+    interface MotionEventHandler {
+        boolean handleMotionEvent(MotionEvent ev);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
similarity index 76%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index ee160a1..0191c60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -19,28 +19,28 @@
 /**
  * Callback called when receiving drag-resize or drag-move related input events.
  */
-public interface DragResizeCallback {
+public interface DragPositioningCallback {
     /**
-     * Called when a drag resize starts.
+     * Called when a drag-resize or drag-move starts.
      *
      * @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use
      *                 {@code 0} to indicate it's a move
-     * @param x x coordinate in window decoration coordinate system where the drag resize starts
-     * @param y y coordinate in window decoration coordinate system where the drag resize starts
+     * @param x x coordinate in window decoration coordinate system where the drag starts
+     * @param y y coordinate in window decoration coordinate system where the drag starts
      */
-    void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
+    void onDragPositioningStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
 
     /**
-     * Called when the pointer moves during a drag resize.
+     * Called when the pointer moves during a drag-resize or drag-move.
      * @param x x coordinate in window decoration coordinate system of the new pointer location
      * @param y y coordinate in window decoration coordinate system of the new pointer location
      */
-    void onDragResizeMove(float x, float y);
+    void onDragPositioningMove(float x, float y);
 
     /**
-     * Called when a drag resize stops.
+     * Called when a drag-resize or drag-move stops.
      * @param x x coordinate in window decoration coordinate system where the drag resize stops
      * @param y y coordinate in window decoration coordinate system where the drag resize stops
      */
-    void onDragResizeEnd(float x, float y);
+    void onDragPositioningEnd(float x, float y);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index d3f1332..7d954ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -48,7 +48,6 @@
  * Task edges are for resizing with a mouse.
  * Task corners are for resizing with touch input.
  */
-// TODO(b/251270585): investigate how to pass taps in corners to the tasks
 class DragResizeInputListener implements AutoCloseable {
     private static final String TAG = "DragResizeInputListener";
 
@@ -63,7 +62,7 @@
     private final SurfaceControl mDecorationSurface;
     private final InputChannel mInputChannel;
     private final TaskResizeInputEventReceiver mInputEventReceiver;
-    private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback;
+    private final DragPositioningCallback mCallback;
 
     private int mWidth;
     private int mHeight;
@@ -84,7 +83,7 @@
             Choreographer choreographer,
             int displayId,
             SurfaceControl decorationSurface,
-            DragResizeCallback callback) {
+            DragPositioningCallback callback) {
         mInputManager = context.getSystemService(InputManager.class);
         mHandler = handler;
         mChoreographer = choreographer;
@@ -115,7 +114,8 @@
         mInputEventReceiver = new TaskResizeInputEventReceiver(
                 mInputChannel, mHandler, mChoreographer);
         mCallback = callback;
-        mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
+        mDragDetector = new DragDetector(mInputEventReceiver);
+        mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
     }
 
     /**
@@ -223,12 +223,12 @@
         }
     }
 
-    private class TaskResizeInputEventReceiver extends InputEventReceiver {
+    private class TaskResizeInputEventReceiver extends InputEventReceiver
+            implements DragDetector.MotionEventHandler {
         private final Choreographer mChoreographer;
         private final Runnable mConsumeBatchEventRunnable;
         private boolean mConsumeBatchEventScheduled;
         private boolean mShouldHandleEvents;
-        private boolean mDragging;
 
         private TaskResizeInputEventReceiver(
                 InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -270,15 +270,15 @@
             if (!(inputEvent instanceof MotionEvent)) {
                 return false;
             }
+            return mDragDetector.onMotionEvent((MotionEvent) inputEvent);
+        }
 
-            MotionEvent e = (MotionEvent) inputEvent;
+        @Override
+        public boolean handleMotionEvent(MotionEvent e) {
             boolean result = false;
             // Check if this is a touch event vs mouse event.
             // Touch events are tracked in four corners. Other events are tracked in resize edges.
             boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
-            if (isTouch) {
-                mDragging = mDragDetector.detectDragEvent(e);
-            }
             switch (e.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN: {
                     float x = e.getX(0);
@@ -293,7 +293,7 @@
                         float rawX = e.getRawX(0);
                         float rawY = e.getRawY(0);
                         int ctrlType = calculateCtrlType(isTouch, x, y);
-                        mCallback.onDragResizeStart(ctrlType, rawX, rawY);
+                        mCallback.onDragPositioningStart(ctrlType, rawX, rawY);
                         result = true;
                     }
                     break;
@@ -305,24 +305,17 @@
                     int dragPointerIndex = e.findPointerIndex(mDragPointerId);
                     float rawX = e.getRawX(dragPointerIndex);
                     float rawY = e.getRawY(dragPointerIndex);
-                    if (!isTouch) {
-                        // For all other types allow immediate dragging.
-                        mDragging = true;
-                    }
-                    if (mDragging) {
-                        mCallback.onDragResizeMove(rawX, rawY);
-                        result = true;
-                    }
+                    mCallback.onDragPositioningMove(rawX, rawY);
+                    result = true;
                     break;
                 }
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL: {
-                    if (mShouldHandleEvents && mDragging) {
+                    if (mShouldHandleEvents) {
                         int dragPointerIndex = e.findPointerIndex(mDragPointerId);
-                        mCallback.onDragResizeEnd(
+                        mCallback.onDragPositioningEnd(
                                 e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
                     }
-                    mDragging = false;
                     mShouldHandleEvents = false;
                     mDragPointerId = -1;
                     result = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 20631f8..d3f9227 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -23,7 +23,7 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 
-class TaskPositioner implements DragResizeCallback {
+class TaskPositioner implements DragPositioningCallback {
 
     @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
     @interface CtrlType {}
@@ -38,11 +38,9 @@
     private final WindowDecoration mWindowDecoration;
 
     private final Rect mTaskBoundsAtDragStart = new Rect();
-    private final PointF mResizeStartPoint = new PointF();
-    private final Rect mResizeTaskBounds = new Rect();
-    // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
-    // Used to optimized fluid resizing of freeform tasks.
-    private boolean mPendingDragResizeHint = false;
+    private final PointF mRepositionStartPoint = new PointF();
+    private final Rect mRepositionTaskBounds = new Rect();
+    private boolean mHasMoved = false;
 
     private int mCtrlType;
     private DragStartListener mDragStartListener;
@@ -59,72 +57,84 @@
     }
 
     @Override
-    public void onDragResizeStart(int ctrlType, float x, float y) {
-        if (ctrlType != CTRL_TYPE_UNDEFINED) {
-            // The task is being resized, send the |dragResizing| hint to core with the first
-            // bounds-change wct.
-            mPendingDragResizeHint = true;
-        }
+    public void onDragPositioningStart(int ctrlType, float x, float y) {
+        mHasMoved = false;
 
         mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
         mCtrlType = ctrlType;
 
         mTaskBoundsAtDragStart.set(
                 mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
-        mResizeStartPoint.set(x, y);
+        mRepositionStartPoint.set(x, y);
     }
 
     @Override
-    public void onDragResizeMove(float x, float y) {
+    public void onDragPositioningMove(float x, float y) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (changeBounds(wct, x, y)) {
-            if (mPendingDragResizeHint) {
+            // The task is being resized, send the |dragResizing| hint to core with the first
+            // bounds-change wct.
+            if (!mHasMoved && mCtrlType != CTRL_TYPE_UNDEFINED) {
                 // This is the first bounds change since drag resize operation started.
                 wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
-                mPendingDragResizeHint = false;
             }
             mTaskOrganizer.applyTransaction(wct);
+            mHasMoved = true;
         }
     }
 
     @Override
-    public void onDragResizeEnd(float x, float y) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
-        changeBounds(wct, x, y);
-        mTaskOrganizer.applyTransaction(wct);
+    public void onDragPositioningEnd(float x, float y) {
+        // |mHasMoved| being false means there is no real change to the task bounds in WM core, so
+        // we don't need a WCT to finish it.
+        if (mHasMoved) {
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+            changeBounds(wct, x, y);
+            mTaskOrganizer.applyTransaction(wct);
+        }
 
-        mCtrlType = 0;
+        mCtrlType = CTRL_TYPE_UNDEFINED;
         mTaskBoundsAtDragStart.setEmpty();
-        mResizeStartPoint.set(0, 0);
-        mPendingDragResizeHint = false;
+        mRepositionStartPoint.set(0, 0);
+        mHasMoved = false;
     }
 
     private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
-        float deltaX = x - mResizeStartPoint.x;
-        mResizeTaskBounds.set(mTaskBoundsAtDragStart);
+        // |mRepositionTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not
+        // true, we can compare it against |mTaskBoundsAtDragStart|.
+        final int oldLeft = mHasMoved ? mRepositionTaskBounds.left : mTaskBoundsAtDragStart.left;
+        final int oldTop = mHasMoved ? mRepositionTaskBounds.top : mTaskBoundsAtDragStart.top;
+        final int oldRight = mHasMoved ? mRepositionTaskBounds.right : mTaskBoundsAtDragStart.right;
+        final int oldBottom =
+                mHasMoved ? mRepositionTaskBounds.bottom : mTaskBoundsAtDragStart.bottom;
+
+        final float deltaX = x - mRepositionStartPoint.x;
+        final float deltaY = y - mRepositionStartPoint.y;
+        mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
         if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
-            mResizeTaskBounds.left += deltaX;
+            mRepositionTaskBounds.left += deltaX;
         }
         if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
-            mResizeTaskBounds.right += deltaX;
+            mRepositionTaskBounds.right += deltaX;
         }
-        float deltaY = y - mResizeStartPoint.y;
         if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
-            mResizeTaskBounds.top += deltaY;
+            mRepositionTaskBounds.top += deltaY;
         }
         if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
-            mResizeTaskBounds.bottom += deltaY;
+            mRepositionTaskBounds.bottom += deltaY;
         }
-        if (mCtrlType == 0) {
-            mResizeTaskBounds.offset((int) deltaX, (int) deltaY);
+        if (mCtrlType == CTRL_TYPE_UNDEFINED) {
+            mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
         }
 
-        if (!mResizeTaskBounds.isEmpty()) {
-            wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
-            return true;
+        if (oldLeft == mRepositionTaskBounds.left && oldTop == mRepositionTaskBounds.top
+                && oldRight == mRepositionTaskBounds.right
+                && oldBottom == mRepositionTaskBounds.bottom) {
+            return false;
         }
-        return false;
+        wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+        return true;
     }
 
     interface DragStartListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 907977c..3734487 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -29,7 +29,8 @@
  */
 public interface WindowDecorViewModel {
     /**
-     * Sets the transition starter that starts freeform task transitions.
+     * Sets the transition starter that starts freeform task transitions. Only called when
+     * {@link com.android.wm.shell.transition.Transitions#ENABLE_SHELL_TRANSITIONS} is {@code true}.
      *
      * @param transitionStarter the transition starter that starts freeform task transitions
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 7f85988..62b72f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -252,9 +252,7 @@
         }
 
         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
-        final int captionWidth = params.mCaptionWidthId == Resources.ID_NULL
-                ? taskBounds.width()
-                : loadDimensionPixelSize(resources, params.mCaptionWidthId);
+        final int captionWidth = taskBounds.width();
 
         startT.setPosition(
                         mCaptionContainerSurface,
@@ -428,7 +426,6 @@
         RunningTaskInfo mRunningTaskInfo;
         int mLayoutResId;
         int mCaptionHeightId;
-        int mCaptionWidthId;
         int mShadowRadiusId;
 
         int mOutsetTopId;
@@ -454,7 +451,6 @@
         void reset() {
             mLayoutResId = Resources.ID_NULL;
             mCaptionHeightId = Resources.ID_NULL;
-            mCaptionWidthId = Resources.ID_NULL;
             mShadowRadiusId = Resources.ID_NULL;
 
             mOutsetTopId = Resources.ID_NULL;
diff --git a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
index 8949a75..27d40b2 100644
--- a/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/tests/unittest/res/values/dimen.xml
@@ -17,7 +17,7 @@
 <resources>
     <!-- Resources used in WindowDecorationTests -->
     <dimen name="test_freeform_decor_caption_height">32dp</dimen>
-    <dimen name="test_freeform_decor_caption_width">216dp</dimen>
+    <dimen name="test_freeform_decor_caption_menu_width">216dp</dimen>
     <dimen name="test_window_decor_left_outset">10dp</dimen>
     <dimen name="test_window_decor_top_outset">20dp</dimen>
     <dimen name="test_window_decor_right_outset">30dp</dimen>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index ff1d2990..d5bb901 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -28,9 +28,11 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -82,13 +84,14 @@
     @Mock
     SyncTransactionQueue mSyncQueue;
     @Mock
-    TaskViewTransitions mTaskViewTransitions;
+    Transitions mTransitions;
 
     SurfaceSession mSession;
     SurfaceControl mLeash;
 
     Context mContext;
     TaskView mTaskView;
+    TaskViewTransitions mTaskViewTransitions;
 
     @Before
     public void setUp() {
@@ -118,6 +121,10 @@
             return null;
         }).when(mSyncQueue).runInSync(any());
 
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            doReturn(true).when(mTransitions).isRegistered();
+        }
+        mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
         mTaskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
         mTaskView.setListener(mExecutor, mViewListener);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
index 1636c5f..0a31338 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -21,7 +21,6 @@
 import android.util.SparseArray
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.bubbles.storage.BubbleXmlHelperTest.Companion.sparseArraysEqual
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertNotNull
 import junit.framework.Assert.assertTrue
@@ -36,7 +35,8 @@
 
     // user, package, shortcut, notification key, height, res-height, title, taskId, locusId
     private val user0Bubbles = listOf(
-            BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null),
+            BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null,
+                    true),
             BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2,
                     null),
             BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null,
@@ -44,7 +44,8 @@
     )
 
     private val user1Bubbles = listOf(
-            BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null),
+            BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null,
+                    true),
             BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4,
                     null),
             BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null,
@@ -76,6 +77,6 @@
         assertEquals(actual.size(), 0)
 
         repository.persistsToDisk(bubbles)
-        assertTrue(sparseArraysEqual(bubbles, repository.readFromDisk()))
+        assertTrue(bubbles.contentEquals(repository.readFromDisk()))
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
index 4ab9f87..3bfbcd2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
@@ -34,7 +34,8 @@
 class BubbleXmlHelperTest : ShellTestCase() {
 
     private val user0Bubbles = listOf(
-            BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1),
+            BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1,
+                    isDismissable = true),
             BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2,
                     null),
             BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null,
@@ -42,7 +43,8 @@
     )
 
     private val user1Bubbles = listOf(
-            BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3),
+            BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3,
+                    isDismissable = true),
             BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4,
                     null),
             BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null,
@@ -51,28 +53,6 @@
 
     private val bubbles = SparseArray<List<BubbleEntity>>()
 
-    // Checks that the contents of the two sparse arrays are the same.
-    companion object {
-        fun sparseArraysEqual(
-            one: SparseArray<List<BubbleEntity>>?,
-            two: SparseArray<List<BubbleEntity>>?
-        ): Boolean {
-            if (one == null && two == null) return true
-            if ((one == null) != (two == null)) return false
-            if (one!!.size() != two!!.size()) return false
-            for (i in 0 until one.size()) {
-                val k1 = one.keyAt(i)
-                val v1 = one.valueAt(i)
-                val k2 = two.keyAt(i)
-                val v2 = two.valueAt(i)
-                if (k1 != k2 && v1 != v2) {
-                    return false
-                }
-            }
-            return true
-        }
-    }
-
     @Before
     fun setup() {
         bubbles.put(0, user0Bubbles)
@@ -83,14 +63,14 @@
     fun testWriteXml() {
         val expectedEntries = """
 <bs uid="0">
-<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" />
-<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" />
-<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" />
+<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" d="true" />
+<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" d="false" />
+<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" d="false" />
 </bs>
 <bs uid="1">
-<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" />
-<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" />
-<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" />
+<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" d="true" />
+<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" d="false" />
+<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" d="false" />
 </bs>
         """.trimIndent()
         ByteArrayOutputStream().use {
@@ -107,19 +87,19 @@
 <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
 <bs v="2">
 <bs uid="0">
-<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" />
-<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" />
-<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" />
+<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" d="true" />
+<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" d="false" />
+<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" d="false" />
 </bs>
 <bs uid="1">
-<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" />
-<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" />
-<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" />
+<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" d="true" />
+<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" d="false" />
+<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" d="false" />
 </bs>
 </bs>
         """.trimIndent()
         val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
-        assertTrue("failed parsing bubbles from xml\n$src", sparseArraysEqual(bubbles, actual))
+        assertTrue("failed parsing bubbles from xml\n$src", bubbles.contentEquals(actual))
     }
 
     // V0 -> V1 happened prior to release / during dogfood so nothing is saved
@@ -161,8 +141,7 @@
 </bs>
         """.trimIndent()
         val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
-        assertTrue("failed parsing bubbles from xml\n$src",
-                sparseArraysEqual(expectedBubbles, actual))
+        assertTrue("failed parsing bubbles from xml\n$src", expectedBubbles.contentEquals(actual))
     }
 
     /**
@@ -187,7 +166,7 @@
         """.trimIndent()
         val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
         assertTrue("failed parsing bubbles from xml\n$src",
-                sparseArraysEqual(expectedBubbles, actual))
+                expectedBubbles.contentEquals(actual))
     }
 
     @Test
@@ -210,6 +189,6 @@
         )
         val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
         assertTrue("failed parsing bubbles from xml\n$src",
-                sparseArraysEqual(expectedBubbles, actual))
+                expectedBubbles.contentEquals(actual))
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 2fc0914..875e610 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -94,7 +94,10 @@
     private @Mock Lazy<Transitions> mMockTransitionsLazy;
     private @Mock CompatUIWindowManager mMockCompatLayout;
     private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
+    private @Mock RestartDialogWindowManager mMockRestartDialogLayout;
     private @Mock DockStateReader mDockStateReader;
+    private @Mock CompatUIConfiguration mCompatUIConfiguration;
+    private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler;
 
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -112,10 +115,17 @@
         doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
         doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
         doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
+
+        doReturn(DISPLAY_ID).when(mMockRestartDialogLayout).getDisplayId();
+        doReturn(TASK_ID).when(mMockRestartDialogLayout).getTaskId();
+        doReturn(true).when(mMockRestartDialogLayout).createLayout(anyBoolean());
+        doReturn(true).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
+
         mShellInit = spy(new ShellInit(mMockExecutor));
         mController = new CompatUIController(mContext, mShellInit, mMockShellController,
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
-                mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader) {
+                mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
+                mCompatUIConfiguration, mCompatUIShellCommandHandler) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
@@ -127,6 +137,12 @@
                     TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
                 return mMockLetterboxEduLayout;
             }
+
+            @Override
+            RestartDialogWindowManager createRestartDialogWindowManager(Context context,
+                    TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
+                return mMockRestartDialogLayout;
+            }
         };
         mShellInit.init();
         spyOn(mController);
@@ -159,6 +175,8 @@
         verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
         verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
                 eq(mMockTaskListener));
+        verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+                eq(mMockTaskListener));
 
         // Verify that the compat controls and letterbox education are updated with new size compat
         // info.
@@ -167,10 +185,12 @@
                 CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
-                true);
-        verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
-                true);
+        verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ true);
+        verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ true);
+        verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ true);
 
         // Verify that compat controls and letterbox education are removed with null task listener.
         clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
@@ -180,12 +200,14 @@
 
         verify(mMockCompatLayout).release();
         verify(mMockLetterboxEduLayout).release();
+        verify(mMockRestartDialogLayout).release();
     }
 
     @Test
     public void testOnCompatInfoChanged_createLayoutReturnsFalse() {
         doReturn(false).when(mMockCompatLayout).createLayout(anyBoolean());
         doReturn(false).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
+        doReturn(false).when(mMockRestartDialogLayout).createLayout(anyBoolean());
 
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -194,6 +216,8 @@
         verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
         verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
                 eq(mMockTaskListener));
+        verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+                eq(mMockTaskListener));
 
         // Verify that the layout is created again.
         clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
@@ -201,15 +225,19 @@
 
         verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
         verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+        verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
         verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
         verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
                 eq(mMockTaskListener));
+        verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+                eq(mMockTaskListener));
     }
 
     @Test
     public void testOnCompatInfoChanged_updateCompatInfoReturnsFalse() {
         doReturn(false).when(mMockCompatLayout).updateCompatInfo(any(), any(), anyBoolean());
         doReturn(false).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
+        doReturn(false).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
 
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_HIDDEN);
@@ -218,24 +246,33 @@
         verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
         verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
                 eq(mMockTaskListener));
+        verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+                eq(mMockTaskListener));
 
-        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
+                mController);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
-                true);
-        verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
-                true);
+        verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ true);
+        verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ true);
+        verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ true);
 
         // Verify that the layout is created again.
-        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mController);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout,
+                mController);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
         verify(mMockCompatLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
         verify(mMockLetterboxEduLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
+        verify(mMockRestartDialogLayout, never()).updateCompatInfo(any(), any(), anyBoolean());
         verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
         verify(mController).createLetterboxEduWindowManager(any(), eq(taskInfo),
                 eq(mMockTaskListener));
+        verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
+                eq(mMockTaskListener));
     }
 
 
@@ -259,6 +296,7 @@
 
         verify(mMockCompatLayout, never()).release();
         verify(mMockLetterboxEduLayout, never()).release();
+        verify(mMockRestartDialogLayout, never()).release();
         verify(mMockDisplayInsetsController, never()).removeInsetsChangedListener(eq(DISPLAY_ID),
                 any());
 
@@ -267,6 +305,7 @@
         verify(mMockDisplayInsetsController).removeInsetsChangedListener(eq(DISPLAY_ID), any());
         verify(mMockCompatLayout).release();
         verify(mMockLetterboxEduLayout).release();
+        verify(mMockRestartDialogLayout).release();
     }
 
     @Test
@@ -278,11 +317,13 @@
 
         verify(mMockCompatLayout, never()).updateDisplayLayout(any());
         verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(any());
+        verify(mMockRestartDialogLayout, never()).updateDisplayLayout(any());
 
         mController.onDisplayConfigurationChanged(DISPLAY_ID, new Configuration());
 
         verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
         verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
+        verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout);
     }
 
     @Test
@@ -301,12 +342,14 @@
 
         verify(mMockCompatLayout).updateDisplayLayout(mMockDisplayLayout);
         verify(mMockLetterboxEduLayout).updateDisplayLayout(mMockDisplayLayout);
+        verify(mMockRestartDialogLayout).updateDisplayLayout(mMockDisplayLayout);
 
         // No update if the insets state is the same.
-        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout);
         mOnInsetsChangedListenerCaptor.getValue().insetsChanged(new InsetsState(insetsState));
         verify(mMockCompatLayout, never()).updateDisplayLayout(mMockDisplayLayout);
         verify(mMockLetterboxEduLayout, never()).updateDisplayLayout(mMockDisplayLayout);
+        verify(mMockRestartDialogLayout, never()).updateDisplayLayout(mMockDisplayLayout);
     }
 
     @Test
@@ -319,22 +362,26 @@
 
         verify(mMockCompatLayout).updateVisibility(false);
         verify(mMockLetterboxEduLayout).updateVisibility(false);
+        verify(mMockRestartDialogLayout).updateVisibility(false);
 
         // Verify button remains hidden while IME is showing.
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_HIDDEN);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
-                false);
-        verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
-                false);
+        verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ false);
+        verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ false);
+        verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ false);
 
         // Verify button is shown after IME is hidden.
         mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
 
         verify(mMockCompatLayout).updateVisibility(true);
         verify(mMockLetterboxEduLayout).updateVisibility(true);
+        verify(mMockRestartDialogLayout).updateVisibility(true);
     }
 
     @Test
@@ -347,22 +394,26 @@
 
         verify(mMockCompatLayout).updateVisibility(false);
         verify(mMockLetterboxEduLayout).updateVisibility(false);
+        verify(mMockRestartDialogLayout).updateVisibility(false);
 
         // Verify button remains hidden while keyguard is showing.
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
                 CAMERA_COMPAT_CONTROL_HIDDEN);
         mController.onCompatInfoChanged(taskInfo, mMockTaskListener);
 
-        verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
-                false);
-        verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener, /* canShow= */
-                false);
+        verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ false);
+        verify(mMockLetterboxEduLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ false);
+        verify(mMockRestartDialogLayout).updateCompatInfo(taskInfo, mMockTaskListener,
+                /* canShow= */ false);
 
         // Verify button is shown after keyguard becomes not showing.
         mController.onKeyguardVisibilityChanged(false, false, false);
 
         verify(mMockCompatLayout).updateVisibility(true);
         verify(mMockLetterboxEduLayout).updateVisibility(true);
+        verify(mMockRestartDialogLayout).updateVisibility(true);
     }
 
     @Test
@@ -375,20 +426,23 @@
 
         verify(mMockCompatLayout, times(2)).updateVisibility(false);
         verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
+        verify(mMockRestartDialogLayout, times(2)).updateVisibility(false);
 
-        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout);
 
         // Verify button remains hidden after keyguard becomes not showing since IME is showing.
         mController.onKeyguardVisibilityChanged(false, false, false);
 
         verify(mMockCompatLayout).updateVisibility(false);
         verify(mMockLetterboxEduLayout).updateVisibility(false);
+        verify(mMockRestartDialogLayout).updateVisibility(false);
 
         // Verify button is shown after IME is not showing.
         mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
 
         verify(mMockCompatLayout).updateVisibility(true);
         verify(mMockLetterboxEduLayout).updateVisibility(true);
+        verify(mMockRestartDialogLayout).updateVisibility(true);
     }
 
     @Test
@@ -401,20 +455,23 @@
 
         verify(mMockCompatLayout, times(2)).updateVisibility(false);
         verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
+        verify(mMockRestartDialogLayout, times(2)).updateVisibility(false);
 
-        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
+        clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout, mMockRestartDialogLayout);
 
         // Verify button remains hidden after IME is hidden since keyguard is showing.
         mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ false);
 
         verify(mMockCompatLayout).updateVisibility(false);
         verify(mMockLetterboxEduLayout).updateVisibility(false);
+        verify(mMockRestartDialogLayout).updateVisibility(false);
 
         // Verify button is shown after keyguard becomes not showing.
         mController.onKeyguardVisibilityChanged(false, false, false);
 
         verify(mMockCompatLayout).updateVisibility(true);
         verify(mMockLetterboxEduLayout).updateVisibility(true);
+        verify(mMockRestartDialogLayout).updateVisibility(true);
     }
 
     private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 7d3e718..5f294d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -31,6 +31,7 @@
 import android.app.TaskInfo;
 import android.app.TaskInfo.CameraCompatControlState;
 import android.testing.AndroidTestingRunner;
+import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.SurfaceControlViewHost;
 import android.widget.ImageButton;
@@ -45,12 +46,17 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
 
+import junit.framework.Assert;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Consumer;
+
 /**
  * Tests for {@link CompatUILayout}.
  *
@@ -65,20 +71,22 @@
 
     @Mock private SyncTransactionQueue mSyncTransactionQueue;
     @Mock private CompatUIController.CompatUICallback mCallback;
+    @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
     @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
     @Mock private SurfaceControlViewHost mViewHost;
+    @Mock private CompatUIConfiguration mCompatUIConfiguration;
 
     private CompatUIWindowManager mWindowManager;
     private CompatUILayout mLayout;
+    private TaskInfo mTaskInfo;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
-        mWindowManager = new CompatUIWindowManager(mContext,
-                createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
-                mSyncTransactionQueue, mCallback, mTaskListener,
-                new DisplayLayout(), new CompatUIHintsState());
+        mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+        mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+                mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+                mCompatUIConfiguration, mOnRestartButtonClicked);
 
         mLayout = (CompatUILayout)
                 LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
@@ -95,8 +103,15 @@
         final ImageButton button = mLayout.findViewById(R.id.size_compat_restart_button);
         button.performClick();
 
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> restartCaptor =
+                ArgumentCaptor.forClass(Pair.class);
+
         verify(mWindowManager).onRestartButtonClicked();
-        verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
+        verify(mOnRestartButtonClicked).accept(restartCaptor.capture());
+        final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = restartCaptor.getValue();
+        Assert.assertEquals(mTaskInfo, result.first);
+        Assert.assertEquals(mTaskListener, result.second);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index e79b803..0c5edc3f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -38,6 +39,7 @@
 import android.app.TaskInfo;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
+import android.util.Pair;
 import android.view.DisplayInfo;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -53,12 +55,17 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
 
+import junit.framework.Assert;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Consumer;
+
 /**
  * Tests for {@link CompatUIWindowManager}.
  *
@@ -73,20 +80,22 @@
 
     @Mock private SyncTransactionQueue mSyncTransactionQueue;
     @Mock private CompatUIController.CompatUICallback mCallback;
+    @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
     @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
     @Mock private CompatUILayout mLayout;
     @Mock private SurfaceControlViewHost mViewHost;
+    @Mock private CompatUIConfiguration mCompatUIConfiguration;
 
     private CompatUIWindowManager mWindowManager;
+    private TaskInfo mTaskInfo;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-
-        mWindowManager = new CompatUIWindowManager(mContext,
-                createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN),
-                mSyncTransactionQueue, mCallback, mTaskListener,
-                new DisplayLayout(), new CompatUIHintsState());
+        mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
+        mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+                mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+                mCompatUIConfiguration, mOnRestartButtonClicked);
 
         spyOn(mWindowManager);
         doReturn(mLayout).when(mWindowManager).inflateLayout();
@@ -351,14 +360,14 @@
         mWindowManager.updateVisibility(/* canShow= */ false);
 
         verify(mWindowManager, never()).createLayout(anyBoolean());
-        verify(mLayout).setVisibility(View.GONE);
+        verify(mLayout, atLeastOnce()).setVisibility(View.GONE);
 
         // Show button.
         doReturn(View.GONE).when(mLayout).getVisibility();
         mWindowManager.updateVisibility(/* canShow= */ true);
 
         verify(mWindowManager, never()).createLayout(anyBoolean());
-        verify(mLayout).setVisibility(View.VISIBLE);
+        verify(mLayout, atLeastOnce()).setVisibility(View.VISIBLE);
     }
 
     @Test
@@ -404,7 +413,14 @@
     public void testOnRestartButtonClicked() {
         mWindowManager.onRestartButtonClicked();
 
-        verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> restartCaptor =
+                ArgumentCaptor.forClass(Pair.class);
+
+        verify(mOnRestartButtonClicked).accept(restartCaptor.capture());
+        final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = restartCaptor.getValue();
+        Assert.assertEquals(mTaskInfo, result.first);
+        Assert.assertEquals(mTaskListener, result.second);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
new file mode 100644
index 0000000..e2dcdb0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.compatui;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link RestartDialogLayout}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:RestartDialogLayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class RestartDialogLayoutTest extends ShellTestCase  {
+
+    @Mock private Runnable mDismissCallback;
+    @Mock private Consumer<Boolean> mRestartCallback;
+
+    private RestartDialogLayout mLayout;
+    private View mDismissButton;
+    private View mRestartButton;
+    private View mDialogContainer;
+    private CheckBox mDontRepeatCheckBox;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mLayout = (RestartDialogLayout)
+                LayoutInflater.from(mContext).inflate(R.layout.letterbox_restart_dialog_layout,
+                        null);
+        mDismissButton = mLayout.findViewById(R.id.letterbox_restart_dialog_dismiss_button);
+        mRestartButton = mLayout.findViewById(R.id.letterbox_restart_dialog_restart_button);
+        mDialogContainer = mLayout.findViewById(R.id.letterbox_restart_dialog_container);
+        mDontRepeatCheckBox = mLayout.findViewById(R.id.letterbox_restart_dialog_checkbox);
+        mLayout.setDismissOnClickListener(mDismissCallback);
+        mLayout.setRestartOnClickListener(mRestartCallback);
+    }
+
+    @Test
+    public void testOnFinishInflate() {
+        assertEquals(mLayout.getDialogContainerView(),
+                mLayout.findViewById(R.id.letterbox_restart_dialog_container));
+        assertEquals(mLayout.getDialogTitle(),
+                mLayout.findViewById(R.id.letterbox_restart_dialog_title));
+        assertEquals(mLayout.getBackgroundDimDrawable(), mLayout.getBackground());
+        assertEquals(mLayout.getBackground().getAlpha(), 0);
+    }
+
+    @Test
+    public void testOnDismissButtonClicked() {
+        assertTrue(mDismissButton.performClick());
+
+        verify(mDismissCallback).run();
+    }
+
+    @Test
+    public void testOnRestartButtonClickedWithoutCheckbox() {
+        mDontRepeatCheckBox.setChecked(false);
+        assertTrue(mRestartButton.performClick());
+
+        verify(mRestartCallback).accept(false);
+    }
+
+    @Test
+    public void testOnRestartButtonClickedWithCheckbox() {
+        mDontRepeatCheckBox.setChecked(true);
+        assertTrue(mRestartButton.performClick());
+
+        verify(mRestartCallback).accept(true);
+    }
+
+    @Test
+    public void testOnBackgroundClickedDoesntDismiss() {
+        assertFalse(mLayout.performClick());
+
+        verify(mDismissCallback, never()).run();
+    }
+
+    @Test
+    public void testOnDialogContainerClicked() {
+        assertTrue(mDialogContainer.performClick());
+
+        verify(mDismissCallback, never()).run();
+        verify(mRestartCallback, never()).accept(anyBoolean());
+    }
+
+    @Test
+    public void testSetDismissOnClickListenerNull() {
+        mLayout.setDismissOnClickListener(null);
+
+        assertFalse(mDismissButton.performClick());
+        assertFalse(mLayout.performClick());
+        assertTrue(mDialogContainer.performClick());
+
+        verify(mDismissCallback, never()).run();
+    }
+
+    @Test
+    public void testSetRestartOnClickListenerNull() {
+        mLayout.setRestartOnClickListener(null);
+
+        assertFalse(mRestartButton.performClick());
+        assertFalse(mLayout.performClick());
+        assertTrue(mDialogContainer.performClick());
+
+        verify(mRestartCallback, never()).accept(anyBoolean());
+    }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 35cc168..7997a7e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -334,6 +334,41 @@
     }
 
     @Test
+    public void testGetVisibleTaskCount_noTasks_returnsZero() {
+        assertThat(mController.getVisibleTaskCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
+        RunningTaskInfo task1 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+
+        RunningTaskInfo task2 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+
+        assertThat(mController.getVisibleTaskCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
+        RunningTaskInfo task1 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+
+        RunningTaskInfo task2 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, false /* visible */);
+
+        assertThat(mController.getVisibleTaskCount()).isEqualTo(1);
+    }
+
+    @Test
     public void testHandleTransitionRequest_desktopModeNotActive_returnsNull() {
         when(DesktopModeStatus.isActive(any())).thenReturn(false);
         WindowContainerTransaction wct = mController.handleRequest(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 1e43a59..45cb3a0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -141,6 +141,36 @@
     }
 
     @Test
+    fun getVisibleTaskCount() {
+        // No tasks, count is 0
+        assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+
+        // New task increments count to 1
+        repo.updateVisibleFreeformTasks(taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+
+        // Visibility update to same task does not increase count
+        repo.updateVisibleFreeformTasks(taskId = 1, visible = true)
+        assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+
+        // Second task visible increments count
+        repo.updateVisibleFreeformTasks(taskId = 2, visible = true)
+        assertThat(repo.getVisibleTaskCount()).isEqualTo(2)
+
+        // Hiding a task decrements count
+        repo.updateVisibleFreeformTasks(taskId = 1, visible = false)
+        assertThat(repo.getVisibleTaskCount()).isEqualTo(1)
+
+        // Hiding all tasks leaves count at 0
+        repo.updateVisibleFreeformTasks(taskId = 2, visible = false)
+        assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+
+        // Hiding a not existing task, count remains at 0
+        repo.updateVisibleFreeformTasks(taskId = 999, visible = false)
+        assertThat(repo.getVisibleTaskCount()).isEqualTo(0)
+    }
+
+    @Test
     fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
         repo.addOrMoveFreeformTaskToTop(5)
         repo.addOrMoveFreeformTaskToTop(6)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 4011d08..f16beee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -197,6 +197,27 @@
     }
 
     @Test
+    fun getVisibleTaskCount_noTasks_returnsZero() {
+        assertThat(controller.getVisibleTaskCount()).isEqualTo(0)
+    }
+
+    @Test
+    fun getVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
+        setUpHomeTask()
+        setUpFreeformTask().also(::markTaskVisible)
+        setUpFreeformTask().also(::markTaskVisible)
+        assertThat(controller.getVisibleTaskCount()).isEqualTo(2)
+    }
+
+    @Test
+    fun getVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
+        setUpHomeTask()
+        setUpFreeformTask().also(::markTaskVisible)
+        setUpFreeformTask().also(::markTaskHidden)
+        assertThat(controller.getVisibleTaskCount()).isEqualTo(1)
+    }
+
+    @Test
     fun moveToDesktop() {
         val task = setUpFullscreenTask()
         controller.moveToDesktop(task)
@@ -224,7 +245,7 @@
             assertThat(hierarchyOps).hasSize(3)
             assertReorderSequence(homeTask, freeformTask, fullscreenTask)
             assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
-                    .isEqualTo(WINDOWING_MODE_FREEFORM)
+                .isEqualTo(WINDOWING_MODE_FREEFORM)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 298d0a6..ec264a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -32,6 +32,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -57,17 +58,22 @@
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
     private DisplayInfo mDefaultDisplayInfo;
     private PipBoundsState mPipBoundsState;
+    private PipSizeSpecHandler mPipSizeSpecHandler;
 
 
     @Before
     public void setUp() throws Exception {
         initializeMockResources();
-        mPipBoundsState = new PipBoundsState(mContext);
+        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
-                new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {});
+                new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
+                mPipSizeSpecHandler);
 
-        mPipBoundsState.setDisplayLayout(
-                new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true));
+        DisplayLayout layout =
+                new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
+        mPipBoundsState.setDisplayLayout(layout);
+        mPipSizeSpecHandler.setDisplayLayout(layout);
     }
 
     private void initializeMockResources() {
@@ -120,9 +126,7 @@
 
     @Test
     public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
-        final Size defaultSize = mPipBoundsAlgorithm.getSizeForAspectRatio(DEFAULT_ASPECT_RATIO,
-                DEFAULT_MIN_EDGE_SIZE, mDefaultDisplayInfo.logicalWidth,
-                mDefaultDisplayInfo.logicalHeight);
+        final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO);
 
         mPipBoundsState.setOverrideMinSize(null);
         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
@@ -296,9 +300,9 @@
                 (MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
         };
         final Size[] minimalSizes = new Size[] {
-                new Size((int) (100 * aspectRatios[0]), 100),
-                new Size((int) (100 * aspectRatios[1]), 100),
-                new Size((int) (100 * aspectRatios[2]), 100)
+                new Size((int) (200 * aspectRatios[0]), 200),
+                new Size((int) (200 * aspectRatios[1]), 200),
+                new Size((int) (200 * aspectRatios[2]), 200)
         };
         for (int i = 0; i < aspectRatios.length; i++) {
             final float aspectRatio = aspectRatios[i];
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 8e30f65..341a451 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -33,6 +33,7 @@
 
 import com.android.internal.util.function.TriConsumer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -57,7 +58,7 @@
 
     @Before
     public void setUp() {
-        mPipBoundsState = new PipBoundsState(mContext);
+        mPipBoundsState = new PipBoundsState(mContext, new PipSizeSpecHandler(mContext));
         mTestComponentName1 = new ComponentName(mContext, "component1");
         mTestComponentName2 = new ComponentName(mContext, "component2");
     }
@@ -161,10 +162,10 @@
     @Test
     public void testSetOverrideMinSize_notChanged_callbackNotInvoked() {
         final Runnable callback = mock(Runnable.class);
-        mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+        mPipBoundsState.setOverrideMinSize(new Size(100, 150));
         mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
 
-        mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+        mPipBoundsState.setOverrideMinSize(new Size(100, 150));
 
         verify(callback, never()).run();
     }
@@ -174,11 +175,11 @@
         mPipBoundsState.setOverrideMinSize(null);
         assertEquals(0, mPipBoundsState.getOverrideMinEdgeSize());
 
-        mPipBoundsState.setOverrideMinSize(new Size(5, 10));
-        assertEquals(5, mPipBoundsState.getOverrideMinEdgeSize());
+        mPipBoundsState.setOverrideMinSize(new Size(100, 110));
+        assertEquals(100, mPipBoundsState.getOverrideMinEdgeSize());
 
-        mPipBoundsState.setOverrideMinSize(new Size(15, 10));
-        assertEquals(10, mPipBoundsState.getOverrideMinEdgeSize());
+        mPipBoundsState.setOverrideMinSize(new Size(150, 200));
+        assertEquals(150, mPipBoundsState.getOverrideMinEdgeSize());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 17e7d74..2da4af8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -53,6 +53,7 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import org.junit.Before;
@@ -86,6 +87,7 @@
     private PipBoundsState mPipBoundsState;
     private PipTransitionState mPipTransitionState;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
+    private PipSizeSpecHandler mPipSizeSpecHandler;
 
     private ComponentName mComponent1;
     private ComponentName mComponent2;
@@ -95,10 +97,12 @@
         MockitoAnnotations.initMocks(this);
         mComponent1 = new ComponentName(mContext, "component1");
         mComponent2 = new ComponentName(mContext, "component2");
-        mPipBoundsState = new PipBoundsState(mContext);
+        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
         mPipTransitionState = new PipTransitionState();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
-                new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {});
+                new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
+                mPipSizeSpecHandler);
         mMainExecutor = new TestShellExecutor();
         mPipTaskOrganizer = new PipTaskOrganizer(mContext,
                 mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
@@ -253,8 +257,10 @@
 
     private void preparePipTaskOrg() {
         final DisplayInfo info = new DisplayInfo();
-        mPipBoundsState.setDisplayLayout(new DisplayLayout(info,
-                mContext.getResources(), true, true));
+        DisplayLayout layout = new DisplayLayout(info,
+                mContext.getResources(), true, true);
+        mPipBoundsState.setDisplayLayout(layout);
+        mPipSizeSpecHandler.setDisplayLayout(layout);
         mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
         mPipTaskOrganizer.setSurfaceControlTransactionFactory(
                 MockSurfaceControlHelper::createMockSurfaceControlTransaction);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 35c09a1..4a68287 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -65,7 +65,6 @@
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -108,6 +107,7 @@
     @Mock private PipMotionHelper mMockPipMotionHelper;
     @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
     @Mock private PipBoundsState mMockPipBoundsState;
+    @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
     @Mock private TaskStackListenerImpl mMockTaskStackListener;
     @Mock private ShellExecutor mMockExecutor;
     @Mock private Optional<OneHandedController> mMockOneHandedController;
@@ -130,11 +130,12 @@
         mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
                 mShellController, mMockDisplayController, mMockPipAnimationController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
-                mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
-                mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
-                mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
-                mMockTaskStackListener, mMockPipParamsChangedForwarder,
-                mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor);
+                mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
+                mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
+                mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
+                mMockWindowManagerShellWrapper, mMockTaskStackListener,
+                mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+                mMockOneHandedController, mMockExecutor);
         mShellInit.init();
         when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
         when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -220,11 +221,12 @@
         assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
                 mShellController, mMockDisplayController, mMockPipAnimationController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
-                mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
-                mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
-                mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
-                mMockTaskStackListener, mMockPipParamsChangedForwarder,
-                mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor));
+                mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
+                mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
+                mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
+                mMockWindowManagerShellWrapper, mMockTaskStackListener,
+                mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+                mMockOneHandedController, mMockExecutor));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index c1993b2..c7b9eb3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -85,15 +85,18 @@
 
     private PipBoundsState mPipBoundsState;
 
+    private PipSizeSpecHandler mPipSizeSpecHandler;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mPipBoundsState = new PipBoundsState(mContext);
+        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
         final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
         final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
                 new PipKeepClearAlgorithmInterface() {};
         final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
-                mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm);
+                mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler);
         final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
                 mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
new file mode 100644
index 0000000..d9ff7d1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.wm.shell.pip.phone;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.testing.AndroidTestingRunner;
+import android.util.Size;
+import android.view.DisplayInfo;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Unit test against {@link PipSizeSpecHandler} with feature flag on.
+ */
+@RunWith(AndroidTestingRunner.class)
+public class PipSizeSpecHandlerTest extends ShellTestCase {
+    /** A sample overridden min edge size. */
+    private static final int OVERRIDE_MIN_EDGE_SIZE = 40;
+    /** A sample default min edge size */
+    private static final int DEFAULT_MIN_EDGE_SIZE = 40;
+    /** Display edge size */
+    private static final int DISPLAY_EDGE_SIZE = 1000;
+    /** Default sizing percentage */
+    private static final float DEFAULT_PERCENT = 0.6f;
+    /** Minimum sizing percentage */
+    private static final float MIN_PERCENT = 0.5f;
+    /** Aspect ratio that the new PIP size spec logic optimizes for. */
+    private static final float OPTIMIZED_ASPECT_RATIO = 9f / 16;
+
+    /** A map of aspect ratios to be tested to expected sizes */
+    private static Map<Float, Size> sExpectedMaxSizes;
+    private static Map<Float, Size> sExpectedDefaultSizes;
+    private static Map<Float, Size> sExpectedMinSizes;
+    /** A static mockito session object to mock {@link SystemProperties} */
+    private static StaticMockitoSession sStaticMockitoSession;
+
+    @Mock private Context mContext;
+    @Mock private Resources mResources;
+
+    private PipSizeSpecHandler mPipSizeSpecHandler;
+
+    /**
+     * Sets up static Mockito session for SystemProperties and mocks necessary static methods.
+     */
+    private static void setUpStaticSystemPropertiesSession() {
+        sStaticMockitoSession = mockitoSession()
+                .mockStatic(SystemProperties.class).startMocking();
+        // make sure the feature flag is on
+        when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true);
+        when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
+            String property = invocation.getArgument(0);
+            if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
+                return Float.toString(DEFAULT_PERCENT);
+            } else if (property.equals("com.android.wm.shell.pip.phone.min_percentage")) {
+                return Float.toString(MIN_PERCENT);
+            }
+
+            // throw an exception if illegal arguments are used for these tests
+            throw new InvalidUseOfMatchersException(
+                String.format("Argument %s does not match", property)
+            );
+        });
+    }
+
+    /**
+     * Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
+     */
+    private static void initExpectedSizes() {
+        sExpectedMaxSizes = new HashMap<>();
+        sExpectedDefaultSizes = new HashMap<>();
+        sExpectedMinSizes = new HashMap<>();
+
+        sExpectedMaxSizes.put(16f / 9, new Size(1000, 562));
+        sExpectedDefaultSizes.put(16f / 9, new Size(600, 337));
+        sExpectedMinSizes.put(16f / 9, new Size(499, 281));
+
+        sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
+        sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
+        sExpectedMinSizes.put(4f / 3, new Size(445, 334));
+
+        sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
+        sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
+        sExpectedMinSizes.put(3f / 4, new Size(334, 445));
+
+        sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
+        sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
+        sExpectedMinSizes.put(9f / 16, new Size(281, 499));
+    }
+
+    private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
+            Function<Float, Size> callback) {
+        for (Map.Entry<Float, Size> expectedSizesEntry : expectedSizes.entrySet()) {
+            float aspectRatio = expectedSizesEntry.getKey();
+            Size expectedSize = expectedSizesEntry.getValue();
+
+            Assert.assertEquals(expectedSize, callback.apply(aspectRatio));
+        }
+    }
+
+    @Before
+    public void setUp() {
+        initExpectedSizes();
+        setUpStaticSystemPropertiesSession();
+
+        when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
+        when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
+        when(mResources.getString(anyInt())).thenReturn("0x0");
+        when(mResources.getDisplayMetrics())
+                .thenReturn(getContext().getResources().getDisplayMetrics());
+
+        // set up the mock context for spec handler specifically
+        when(mContext.getResources()).thenReturn(mResources);
+
+        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+
+        // no overridden min edge size by default
+        mPipSizeSpecHandler.setOverrideMinSize(null);
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
+        displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
+
+        // use the parent context (not the mocked one) to obtain the display layout
+        // this is done to avoid unnecessary mocking while allowing for custom display dimensions
+        DisplayLayout displayLayout = new DisplayLayout(displayInfo, getContext().getResources(),
+                false, false);
+        mPipSizeSpecHandler.setDisplayLayout(displayLayout);
+    }
+
+    @After
+    public void cleanUp() {
+        sStaticMockitoSession.finishMocking();
+    }
+
+    @Test
+    public void testGetMaxSize() {
+        forEveryTestCaseCheck(sExpectedMaxSizes,
+                (aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio));
+    }
+
+    @Test
+    public void testGetDefaultSize() {
+        forEveryTestCaseCheck(sExpectedDefaultSizes,
+                (aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio));
+    }
+
+    @Test
+    public void testGetMinSize() {
+        forEveryTestCaseCheck(sExpectedMinSizes,
+                (aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio));
+    }
+
+    @Test
+    public void testGetSizeForAspectRatio_noOverrideMinSize() {
+        // an initial size with 16:9 aspect ratio
+        Size initSize = new Size(600, 337);
+
+        Size expectedSize = new Size(337, 599);
+        Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+
+        Assert.assertEquals(expectedSize, actualSize);
+    }
+
+    @Test
+    public void testGetSizeForAspectRatio_withOverrideMinSize() {
+        // an initial size with a 1:1 aspect ratio
+        mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE,
+                OVERRIDE_MIN_EDGE_SIZE));
+        // make sure initial size is same as override min size
+        Size initSize = mPipSizeSpecHandler.getOverrideMinSize();
+
+        Size expectedSize = new Size(40, 71);
+        Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+
+        Assert.assertEquals(expectedSize, actualSize);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 8ad2932..5c4863f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.Size;
 
 import androidx.test.filters.SmallTest;
 
@@ -90,6 +91,7 @@
     private PipSnapAlgorithm mPipSnapAlgorithm;
     private PipMotionHelper mMotionHelper;
     private PipResizeGestureHandler mPipResizeGestureHandler;
+    private PipSizeSpecHandler mPipSizeSpecHandler;
 
     private DisplayLayout mDisplayLayout;
     private Rect mInsetBounds;
@@ -103,16 +105,17 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mPipBoundsState = new PipBoundsState(mContext);
+        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
         mPipSnapAlgorithm = new PipSnapAlgorithm();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
-                new PipKeepClearAlgorithmInterface() {});
+                new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
         PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
                 mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator);
         mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
-                mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, pipMotionHelper,
-                mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
+                mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer,
+                pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
         // We aren't actually using ShellInit, so just call init directly
         mPipTouchHandler.onInit();
         mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
@@ -122,6 +125,7 @@
 
         mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
         mPipBoundsState.setDisplayLayout(mDisplayLayout);
+        mPipSizeSpecHandler.setDisplayLayout(mDisplayLayout);
         mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET,
                 mPipBoundsState.getDisplayBounds().top + INSET,
                 mPipBoundsState.getDisplayBounds().right - INSET,
@@ -154,13 +158,17 @@
         mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
                 mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
 
+        // getting the expected min and max size
+        float aspectRatio = (float) mPipBounds.width() / mPipBounds.height();
+        Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio);
+        Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio);
+
         assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
         verify(mPipResizeGestureHandler, times(1))
-                .updateMinSize(mPipBounds.width(), mPipBounds.height());
+                .updateMinSize(expectedMinSize.getWidth(), expectedMinSize.getHeight());
 
         verify(mPipResizeGestureHandler, times(1))
-                .updateMaxSize(shorterLength - 2 * mInsetBounds.left,
-                        shorterLength - 2 * mInsetBounds.left);
+                .updateMaxSize(expectedMaxSize.getWidth(), expectedMaxSize.getHeight());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 65e1ea8..0bb809d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -29,11 +29,13 @@
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -45,6 +47,8 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
@@ -57,6 +61,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -65,6 +70,8 @@
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -98,11 +105,7 @@
     @Mock
     private DisplayInsetsController mDisplayInsetsController;
     @Mock
-    private Transitions mTransitions;
-    @Mock
     private TransactionPool mTransactionPool;
-    @Mock
-    private ShellExecutor mMainExecutor;
 
     private final Rect mBounds1 = new Rect(10, 20, 30, 40);
     private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -112,11 +115,16 @@
     private SurfaceControl mRootLeash;
     private ActivityManager.RunningTaskInfo mRootTask;
     private StageCoordinator mStageCoordinator;
+    private Transitions mTransitions;
+    private final TestShellExecutor mMainExecutor = new TestShellExecutor();
+    private final ShellExecutor mAnimExecutor = new TestShellExecutor();
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
 
     @Before
     @UiThreadTest
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mTransitions = createTestTransitions();
         mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
@@ -329,7 +337,20 @@
 
         mStageCoordinator.onFoldedStateChanged(true);
 
-        verify(mStageCoordinator).onSplitScreenExit();
-        verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            verify(mTaskOrganizer).startNewTransition(eq(TRANSIT_SPLIT_DISMISS), notNull());
+        } else {
+            verify(mStageCoordinator).onSplitScreenExit();
+            verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+        }
+    }
+
+    private Transitions createTestTransitions() {
+        ShellInit shellInit = new ShellInit(mMainExecutor);
+        final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
+                mTaskOrganizer, mTransactionPool, mock(DisplayController.class), mMainExecutor,
+                mMainHandler, mAnimExecutor);
+        shellInit.init();
+        return t;
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
new file mode 100644
index 0000000..8f84008
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.wm.shell.windowdecor
+
+import android.os.SystemClock
+import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
+import android.view.InputDevice
+import androidx.test.filters.SmallTest
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/**
+ * Tests for [DragDetector].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragDetectorTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DragDetectorTest {
+    private val motionEvents = mutableListOf<MotionEvent>()
+
+    @Mock
+    private lateinit var eventHandler: DragDetector.MotionEventHandler
+
+    private lateinit var dragDetector: DragDetector
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        `when`(eventHandler.handleMotionEvent(any())).thenReturn(true)
+
+        dragDetector = DragDetector(eventHandler)
+        dragDetector.setTouchSlop(SLOP)
+    }
+
+    @After
+    fun tearDown() {
+        motionEvents.forEach {
+            it.recycle()
+        }
+        motionEvents.clear()
+    }
+
+    @Test
+    fun testNoMove_passesDownAndUp() {
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+    }
+
+    @Test
+    fun testMoveInSlop_touch_passesDownAndUp() {
+        `when`(eventHandler.handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_DOWN
+        })).thenReturn(false)
+
+        assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+
+        val newX = X + SLOP - 1
+        assertFalse(
+                dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
+        verify(eventHandler, never()).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_MOVE
+        })
+
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+    }
+
+    @Test
+    fun testMoveInSlop_mouse_passesDownMoveAndUp() {
+        `when`(eventHandler.handleMotionEvent(argThat {
+            it.action == MotionEvent.ACTION_DOWN
+        })).thenReturn(false)
+
+        assertFalse(dragDetector.onMotionEvent(
+                createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+                    it.source == InputDevice.SOURCE_MOUSE
+        })
+
+        val newX = X + SLOP - 1
+        assertTrue(dragDetector.onMotionEvent(
+                createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+                    it.source == InputDevice.SOURCE_MOUSE
+        })
+
+        assertTrue(dragDetector.onMotionEvent(
+                createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
+                    it.source == InputDevice.SOURCE_MOUSE
+        })
+    }
+
+    @Test
+    fun testMoveBeyondSlop_passesDownMoveAndUp() {
+        `when`(eventHandler.handleMotionEvent(argThat {
+            it.action == MotionEvent.ACTION_DOWN
+        })).thenReturn(false)
+
+        assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+
+        val newX = X + SLOP + 1
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y &&
+                    it.source == InputDevice.SOURCE_TOUCHSCREEN
+        })
+    }
+
+    @Test
+    fun testPassesHoverEnter() {
+        `when`(eventHandler.handleMotionEvent(argThat {
+            it.action == MotionEvent.ACTION_HOVER_ENTER
+        })).thenReturn(false)
+
+        assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y
+        })
+    }
+
+    @Test
+    fun testPassesHoverMove() {
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y
+        })
+    }
+
+    @Test
+    fun testPassesHoverExit() {
+        assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT)))
+        verify(eventHandler).handleMotionEvent(argThat {
+            return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y
+        })
+    }
+
+    private fun createMotionEvent(action: Int, x: Float = X, y: Float = Y, isTouch: Boolean = true):
+            MotionEvent {
+        val time = SystemClock.uptimeMillis()
+        val ev = MotionEvent.obtain(time, time, action, x, y, 0)
+        ev.source = if (isTouch) InputDevice.SOURCE_TOUCHSCREEN else InputDevice.SOURCE_MOUSE
+        motionEvents.add(ev)
+        return ev
+    }
+
+    companion object {
+        private const val SLOP = 10
+        private const val X = 123f
+        private const val Y = 234f
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index ac10ddb..f185a8a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -1,6 +1,7 @@
 package com.android.wm.shell.windowdecor
 
 import android.app.ActivityManager
+import android.app.WindowConfiguration
 import android.graphics.Rect
 import android.os.IBinder
 import android.testing.AndroidTestingRunner
@@ -10,6 +11,7 @@
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP
 import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
 import org.junit.Before
 import org.junit.Test
@@ -63,8 +65,92 @@
     }
 
     @Test
+    fun testDragResize_notMove_skipsTransactionOnEnd() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        taskPositioner.onDragPositioningEnd(
+                STARTING_BOUNDS.left.toFloat() + 10,
+                STARTING_BOUNDS.top.toFloat() + 10
+        )
+
+        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        taskPositioner.onDragPositioningMove(
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        taskPositioner.onDragPositioningEnd(
+                STARTING_BOUNDS.left.toFloat() + 10,
+                STARTING_BOUNDS.top.toFloat() + 10
+        )
+
+        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+            }
+        })
+    }
+
+    @Test
+    fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() {
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        taskPositioner.onDragPositioningMove(
+                STARTING_BOUNDS.left.toFloat() + 10,
+                STARTING_BOUNDS.top.toFloat()
+        )
+        val rectAfterMove = Rect(STARTING_BOUNDS)
+        rectAfterMove.right += 10
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterMove
+            }
+        })
+
+        taskPositioner.onDragPositioningEnd(
+                STARTING_BOUNDS.left.toFloat() + 10,
+                STARTING_BOUNDS.top.toFloat() + 10
+        )
+        val rectAfterEnd = Rect(rectAfterMove)
+        rectAfterEnd.top += 10
+        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                        change.configuration.windowConfiguration.bounds == rectAfterEnd
+            }
+        })
+    }
+
+    @Test
     fun testDragResize_move_skipsDragResizingFlag() {
-        taskPositioner.onDragResizeStart(
+        taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_UNDEFINED, // Move
                 STARTING_BOUNDS.left.toFloat(),
                 STARTING_BOUNDS.top.toFloat()
@@ -73,12 +159,12 @@
         // Move the task 10px to the right.
         val newX = STARTING_BOUNDS.left.toFloat() + 10
         val newY = STARTING_BOUNDS.top.toFloat()
-        taskPositioner.onDragResizeMove(
+        taskPositioner.onDragPositioningMove(
                 newX,
                 newY
         )
 
-        taskPositioner.onDragResizeEnd(newX, newY)
+        taskPositioner.onDragPositioningEnd(newX, newY)
 
         verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
@@ -91,7 +177,7 @@
 
     @Test
     fun testDragResize_resize_setsDragResizingFlag() {
-        taskPositioner.onDragResizeStart(
+        taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_RIGHT, // Resize right
                 STARTING_BOUNDS.left.toFloat(),
                 STARTING_BOUNDS.top.toFloat()
@@ -100,12 +186,12 @@
         // Resize the task by 10px to the right.
         val newX = STARTING_BOUNDS.right.toFloat() + 10
         val newY = STARTING_BOUNDS.top.toFloat()
-        taskPositioner.onDragResizeMove(
+        taskPositioner.onDragPositioningMove(
                 newX,
                 newY
         )
 
-        taskPositioner.onDragResizeEnd(newX, newY)
+        taskPositioner.onDragPositioningEnd(newX, newY)
 
         verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index ec4f17f..2f5263c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -107,6 +107,7 @@
     private SurfaceControl.Transaction mMockSurfaceControlFinishT;
     private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
     private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
+    private int mCaptionMenuWidthId;
 
     @Before
     public void setUp() {
@@ -116,8 +117,7 @@
 
         mRelayoutParams.mLayoutResId = 0;
         mRelayoutParams.mCaptionHeightId = R.dimen.test_freeform_decor_caption_height;
-        // Caption should have fixed width except in testLayoutResultCalculation_fullWidthCaption()
-        mRelayoutParams.mCaptionWidthId = R.dimen.test_freeform_decor_caption_width;
+        mCaptionMenuWidthId = R.dimen.test_freeform_decor_caption_menu_width;
         mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
 
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
@@ -240,7 +240,7 @@
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
         verify(captionContainerSurfaceBuilder).setContainerLayer();
         verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
-        verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
+        verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
 
         verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
@@ -248,7 +248,7 @@
         verify(mMockSurfaceControlViewHost)
                 .setView(same(mMockView),
                         argThat(lp -> lp.height == 64
-                                && lp.width == 432
+                                && lp.width == 300
                                 && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
         if (ViewRootImpl.CAPTION_ON_SHELL) {
             verify(mMockView).setTaskFocusState(true);
@@ -484,7 +484,6 @@
         final SurfaceControl taskSurface = mock(SurfaceControl.class);
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
 
-        mRelayoutParams.mCaptionWidthId = Resources.ID_NULL;
         windowDecor.relayout(taskInfo);
 
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
@@ -558,7 +557,7 @@
             final Resources resources = mDecorWindowContext.getResources();
             int x = mRelayoutParams.mCaptionX;
             int y = mRelayoutParams.mCaptionY;
-            int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+            int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
             int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
             String name = "Test Window";
             WindowDecoration.AdditionalWindow additionalWindow =
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 798688e..2c139b7 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1337,12 +1337,8 @@
     public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags) {
         Preconditions.checkNotNull(attr, "attr must not be null");
         final IAudioService service = getService();
-        try {
-            service.setVolumeIndexForAttributes(attr, index, flags,
-                    getContext().getOpPackageName(), getContext().getAttributionTag());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        int groupId = getVolumeGroupIdForAttributes(attr);
+        setVolumeGroupVolumeIndex(groupId, index, flags);
     }
 
     /**
@@ -1361,11 +1357,8 @@
     public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
         Preconditions.checkNotNull(attr, "attr must not be null");
         final IAudioService service = getService();
-        try {
-            return service.getVolumeIndexForAttributes(attr);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        int groupId = getVolumeGroupIdForAttributes(attr);
+        return getVolumeGroupVolumeIndex(groupId);
     }
 
     /**
@@ -1382,11 +1375,8 @@
     public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
         Preconditions.checkNotNull(attr, "attr must not be null");
         final IAudioService service = getService();
-        try {
-            return service.getMaxVolumeIndexForAttributes(attr);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        int groupId = getVolumeGroupIdForAttributes(attr);
+        return getVolumeGroupMaxVolumeIndex(groupId);
     }
 
     /**
@@ -1403,8 +1393,168 @@
     public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
         Preconditions.checkNotNull(attr, "attr must not be null");
         final IAudioService service = getService();
+        int groupId = getVolumeGroupIdForAttributes(attr);
+        return getVolumeGroupMinVolumeIndex(groupId);
+    }
+
+    /**
+     * Returns the volume group id associated to the given {@link AudioAttributes}.
+     *
+     * @param attributes The {@link AudioAttributes} to consider.
+     * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given
+     * {@link AudioAttributes} if found,
+     * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise.
+     * @hide
+     */
+    public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
+        Preconditions.checkNotNull(attributes, "Audio Attributes must not be null");
+        return AudioProductStrategy.getVolumeGroupIdForAudioAttributes(attributes,
+                /* fallbackOnDefault= */ false);
+    }
+
+    /**
+     * Sets the volume index for a particular group associated to given id.
+     * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group
+     * id supporting the given {@link AudioAttributes}.
+     *
+     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+     * @param index The volume index to set. See
+     *          {@link #getVolumeGroupMaxVolumeIndex(id)} for the largest valid value
+     *          {@link #getVolumeGroupMinVolumeIndex(id)} for the lowest valid value.
+     * @param flags One or more flags.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setVolumeGroupVolumeIndex(int groupId, int index, int flags) {
+        final IAudioService service = getService();
         try {
-            return service.getMinVolumeIndexForAttributes(attr);
+            service.setVolumeGroupVolumeIndex(groupId, index, flags,
+                    getContext().getOpPackageName(), getContext().getAttributionTag());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the current volume index for a particular group associated to given id.
+     * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group
+     * id supporting the given {@link AudioAttributes}.
+     *
+     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+     * @return The current volume index for the stream.
+     * @hide
+     */
+    @IntRange(from = 0)
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public int getVolumeGroupVolumeIndex(int groupId) {
+        final IAudioService service = getService();
+        try {
+            return service.getVolumeGroupVolumeIndex(groupId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the maximum volume index for a particular group associated to given id.
+     * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group
+     * id supporting the given {@link AudioAttributes}.
+     *
+     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+     * @return The maximum valid volume index for the {@link AudioAttributes}.
+     * @hide
+     */
+    @IntRange(from = 0)
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public int getVolumeGroupMaxVolumeIndex(int groupId) {
+        final IAudioService service = getService();
+        try {
+            return service.getVolumeGroupMaxVolumeIndex(groupId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the minimum volume index for a particular group associated to given id.
+     * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group
+     * id supporting the given {@link AudioAttributes}.
+     *
+     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+     * @return The minimum valid volume index for the {@link AudioAttributes}.
+     * @hide
+     */
+    @IntRange(from = 0)
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public int getVolumeGroupMinVolumeIndex(int groupId) {
+        final IAudioService service = getService();
+        try {
+            return service.getVolumeGroupMinVolumeIndex(groupId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Adjusts the volume of a particular group associated to given id by one step in a direction.
+     * <p> If the volume group is associated to a stream type, it fallbacks on
+     * {@link AudioManager#adjustStreamVolume()} for compatibility reason.
+     * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group
+     * id supporting the given {@link AudioAttributes}.
+     *
+     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+     * @param direction The direction to adjust the volume. One of
+     *            {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
+     *            {@link #ADJUST_SAME}.
+     * @param flags One or more flags.
+     * @throws SecurityException if the adjustment triggers a Do Not Disturb change and the caller
+     * is not granted notification policy access.
+     * @hide
+     */
+    public void adjustVolumeGroupVolume(int groupId, int direction, int flags) {
+        IAudioService service = getService();
+        try {
+            service.adjustVolumeGroupVolume(groupId, direction, flags,
+                    getContext().getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get last audible volume of the group associated to given id before it was muted.
+     * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group
+     * id supporting the given {@link AudioAttributes}.
+     *
+     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+     * @return current volume if not muted, volume before muted otherwise.
+     * @hide
+     */
+    @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
+    @IntRange(from = 0)
+    public int getLastAudibleVolumeGroupVolume(int groupId) {
+        IAudioService service = getService();
+        try {
+            return service.getLastAudibleVolumeGroupVolume(groupId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the current mute state for a particular volume group associated to the given id.
+     * <p> Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group
+     * id supporting the given {@link AudioAttributes}.
+     *
+     * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider.
+     * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id.
+     * @see #adjustAttributesVolume(AudioAttributes, int, int)
+     * @hide
+     */
+    public boolean isVolumeGroupMuted(int groupId) {
+        IAudioService service = getService();
+        try {
+            return service.isVolumeGroupMuted(groupId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -7687,7 +7837,8 @@
         Objects.requireNonNull(device);
         try {
             if (device.getId() == 0) {
-                throw new IllegalArgumentException("In valid device: " + device);
+                Log.w(TAG, "setCommunicationDevice: device not found: " + device);
+                return false;
             }
             return getService().setCommunicationDevice(mICallBack, device.getId());
         } catch (RemoteException e) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ad933e0..88a0321 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -126,14 +126,20 @@
 
     List<AudioVolumeGroup> getAudioVolumeGroups();
 
-    void setVolumeIndexForAttributes(in AudioAttributes aa, int index, int flags,
-            String callingPackage, in String attributionTag);
+    void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage,
+            in String attributionTag);
 
-    int getVolumeIndexForAttributes(in AudioAttributes aa);
+    int getVolumeGroupVolumeIndex(int groupId);
 
-    int getMaxVolumeIndexForAttributes(in AudioAttributes aa);
+    int getVolumeGroupMaxVolumeIndex(int groupId);
 
-    int getMinVolumeIndexForAttributes(in AudioAttributes aa);
+    int getVolumeGroupMinVolumeIndex(int groupId);
+
+    int getLastAudibleVolumeGroupVolume(int groupId);
+
+    boolean isVolumeGroupMuted(int groupId);
+
+    void adjustVolumeGroupVolume(int groupId, int direction, int flags, String callingPackage);
 
     int getLastAudibleStreamVolume(int streamType);
 
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index 31d5967..a792501 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -29,10 +29,10 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * @hide
@@ -142,7 +142,7 @@
      */
     public static int getLegacyStreamTypeForStrategyWithAudioAttributes(
             @NonNull AudioAttributes audioAttributes) {
-        Preconditions.checkNotNull(audioAttributes, "AudioAttributes must not be null");
+        Objects.requireNonNull(audioAttributes, "AudioAttributes must not be null");
         for (final AudioProductStrategy productStrategy :
                 AudioProductStrategy.getAudioProductStrategies()) {
             if (productStrategy.supportsAudioAttributes(audioAttributes)) {
@@ -162,6 +162,30 @@
         return AudioSystem.STREAM_MUSIC;
     }
 
+    /**
+     * @hide
+     * @param attributes the {@link AudioAttributes} to identify VolumeGroupId with
+     * @param fallbackOnDefault if set, allows to fallback on the default group (e.g. the group
+     *                          associated to {@link AudioManager#STREAM_MUSIC}).
+     * @return volume group id associated with the given {@link AudioAttributes} if found,
+     *     default volume group id if fallbackOnDefault is set
+     * <p>By convention, the product strategy with default attributes will be associated to the
+     * default volume group (e.g. associated to {@link AudioManager#STREAM_MUSIC})
+     * or {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} if not found.
+     */
+    public static int getVolumeGroupIdForAudioAttributes(
+            @NonNull AudioAttributes attributes, boolean fallbackOnDefault) {
+        Objects.requireNonNull(attributes, "attributes must not be null");
+        int volumeGroupId = getVolumeGroupIdForAudioAttributesInt(attributes);
+        if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
+            return volumeGroupId;
+        }
+        if (fallbackOnDefault) {
+            return getVolumeGroupIdForAudioAttributesInt(getDefaultAttributes());
+        }
+        return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+    }
+
     private static List<AudioProductStrategy> initializeAudioProductStrategies() {
         ArrayList<AudioProductStrategy> apsList = new ArrayList<AudioProductStrategy>();
         int status = native_list_audio_product_strategies(apsList);
@@ -192,8 +216,8 @@
      */
     private AudioProductStrategy(@NonNull String name, int id,
             @NonNull AudioAttributesGroup[] aag) {
-        Preconditions.checkNotNull(name, "name must not be null");
-        Preconditions.checkNotNull(aag, "AudioAttributesGroups must not be null");
+        Objects.requireNonNull(name, "name must not be null");
+        Objects.requireNonNull(aag, "AudioAttributesGroups must not be null");
         mName = name;
         mId = id;
         mAudioAttributesGroups = aag;
@@ -211,6 +235,15 @@
 
     /**
      * @hide
+     * @return the product strategy ID (which is the generalisation of Car Audio Usage / legacy
+     *         routing_strategy linked to {@link AudioAttributes#getUsage()}).
+     */
+    @NonNull public String getName() {
+        return mName;
+    }
+
+    /**
+     * @hide
      * @return first {@link AudioAttributes} associated to this product strategy.
      */
     @SystemApi
@@ -243,7 +276,7 @@
      */
     @TestApi
     public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) {
-        Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+        Objects.requireNonNull(aa, "AudioAttributes must not be null");
         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
             if (aag.supportsAttributes(aa)) {
                 return aag.getStreamType();
@@ -260,7 +293,7 @@
      */
     @SystemApi
     public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) {
-        Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+        Objects.requireNonNull(aa, "AudioAttributes must not be null");
         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
             if (aag.supportsAttributes(aa)) {
                 return true;
@@ -293,7 +326,7 @@
      */
     @TestApi
     public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) {
-        Preconditions.checkNotNull(aa, "AudioAttributes must not be null");
+        Objects.requireNonNull(aa, "AudioAttributes must not be null");
         for (final AudioAttributesGroup aag : mAudioAttributesGroups) {
             if (aag.supportsAttributes(aa)) {
                 return aag.getVolumeGroupId();
@@ -302,6 +335,17 @@
         return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
     }
 
+    private static int getVolumeGroupIdForAudioAttributesInt(@NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes, "attributes must not be null");
+        for (AudioProductStrategy productStrategy : getAudioProductStrategies()) {
+            int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes);
+            if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
+                return volumeGroupId;
+            }
+        }
+        return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -377,8 +421,8 @@
      */
     private static boolean attributesMatches(@NonNull AudioAttributes refAttr,
             @NonNull AudioAttributes attr) {
-        Preconditions.checkNotNull(refAttr, "refAttr must not be null");
-        Preconditions.checkNotNull(attr, "attr must not be null");
+        Objects.requireNonNull(refAttr, "reference AudioAttributes must not be null");
+        Objects.requireNonNull(attr, "requester's AudioAttributes must not be null");
         String refFormattedTags = TextUtils.join(";", refAttr.getTags());
         String cliFormattedTags = TextUtils.join(";", attr.getTags());
         if (refAttr.equals(DEFAULT_ATTRIBUTES)) {
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 2f4dd8f..408883e 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -1004,9 +1004,10 @@
 
 static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) {
     sp<IDrm> drm = android::DrmUtils::MakeDrm();
+    if (drm == NULL) return env->NewByteArray(0);
+
     std::vector<uint8_t> bv;
     drm->getSupportedSchemes(bv);
-
     jbyteArray jUuidBytes = env->NewByteArray(bv.size());
     env->SetByteArrayRegion(jUuidBytes, 0, bv.size(), reinterpret_cast<const jbyte *>(bv.data()));
     return jUuidBytes;
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index 3ab18db..0424882 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -18,7 +18,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</string>
     <string name="confirmation_title" msgid="3785000297483688997">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toegang geven tot je &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"horloge"</string>
+    <string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string>
     <string name="chooser_title" msgid="2262294130493605839">"Een <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kiezen om te beheren met &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
     <string name="summary_watch" msgid="3002344206574997652">"Deze app is vereist om je <xliff:g id="DEVICE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="APP_NAME">%2$s</xliff:g> kan interactie hebben met je meldingen en toegang krijgen tot rechten voor Telefoon, Sms, Contacten, Agenda, Gesprekslijsten en Apparaten in de buurt."</string>
     <string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
@@ -41,8 +41,8 @@
     <string name="consent_yes" msgid="8344487259618762872">"Toestaan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string>
     <string name="consent_back" msgid="2560683030046918882">"Terug"</string>
-    <string name="permission_sync_confirmation_title" msgid="667074294393493186">"App-rechten overzetten naar je horloge"</string>
-    <string name="permission_sync_summary" msgid="8873391306499120778">"We willen het makkelijker voor je maken om je horloge in te stellen. Daarom gebruiken apps die tijdens het instellen worden geïnstalleerd op je horloge, dezelfde rechten als op je telefoon.\n\n Deze rechten kunnen toegang tot de microfoon en locatie van je horloge omvatten."</string>
+    <string name="permission_sync_confirmation_title" msgid="667074294393493186">"App-rechten overzetten naar je smartwatch"</string>
+    <string name="permission_sync_summary" msgid="8873391306499120778">"We willen het makkelijker voor je maken om je smartwatch in te stellen. Daarom gebruiken apps die tijdens het instellen worden geïnstalleerd op je smartwatch, dezelfde rechten als op je telefoon.\n\n Deze rechten kunnen toegang tot de microfoon en locatie van je smartwatch omvatten."</string>
     <string name="vendor_icon_description" msgid="4445875290032225965">"App-icoon"</string>
     <string name="vendor_header_button_description" msgid="6566660389500630608">"Knop Meer informatie"</string>
 </resources>
diff --git a/packages/PackageInstaller/res/values-ky/strings.xml b/packages/PackageInstaller/res/values-ky/strings.xml
index 2684fd9..0f19fe8 100644
--- a/packages/PackageInstaller/res/values-ky/strings.xml
+++ b/packages/PackageInstaller/res/values-ky/strings.xml
@@ -88,7 +88,7 @@
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Планшетиңиз жана жеке дайын-даректериңиз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам планшетиңизге кандайдыр бир зыян келтирилсе же дайын-даректериңизды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
     <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Сыналгыңыз жана жеке дайын-даректериңиз белгисиз колдонмолордон зыян тартып калышы мүмкүн. Бул колдонмону орнотуп, аны пайдалануудан улам сыналгыңызга кандайдыр бир зыян келтирилсе же дайын-даректериңизды жоготуп алсаңыз, өзүңүз жооптуу болосуз."</string>
     <string name="anonymous_source_continue" msgid="4375745439457209366">"Улантуу"</string>
-    <string name="external_sources_settings" msgid="4046964413071713807">"Жөндөөлөр"</string>
+    <string name="external_sources_settings" msgid="4046964413071713807">"Параметрлер"</string>
     <string name="wear_app_channel" msgid="1960809674709107850">"Тагынма колдонмолорду орнотуу/чыгаруу"</string>
     <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Колдонмолорду орноткучтун билдирмелери"</string>
     <string name="notification_installation_success_message" msgid="6450467996056038442">"Ийгиликтүү орнотулду"</string>
diff --git a/packages/PackageInstaller/res/values-pt-rBR/strings.xml b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
index b9e5be8..43dd331 100644
--- a/packages/PackageInstaller/res/values-pt-rBR/strings.xml
+++ b/packages/PackageInstaller/res/values-pt-rBR/strings.xml
@@ -53,7 +53,7 @@
     <string name="uninstall_application_title" msgid="4045420072401428123">"Desinstalar app"</string>
     <string name="uninstall_update_title" msgid="824411791011583031">"Desinstalar atualização"</string>
     <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> é parte do seguinte app:"</string>
-    <string name="uninstall_application_text" msgid="3816830743706143980">"Quer desinstalar este app?"</string>
+    <string name="uninstall_application_text" msgid="3816830743706143980">"Você quer desinstalar este app?"</string>
     <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Quer desinstalar este app para "<b>"todos"</b>" os usuários? O aplicativo e os dados dele serão removidos para "<b>"todos"</b>" os usuários do dispositivo."</string>
     <string name="uninstall_application_text_user" msgid="498072714173920526">"Quer desinstalar este app para o usuário <xliff:g id="USERNAME">%1$s</xliff:g>?"</string>
     <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Você quer desinstalar esse app do seu perfil de trabalho?"</string>
diff --git a/packages/PackageInstaller/res/values-pt/strings.xml b/packages/PackageInstaller/res/values-pt/strings.xml
index b9e5be8..43dd331 100644
--- a/packages/PackageInstaller/res/values-pt/strings.xml
+++ b/packages/PackageInstaller/res/values-pt/strings.xml
@@ -53,7 +53,7 @@
     <string name="uninstall_application_title" msgid="4045420072401428123">"Desinstalar app"</string>
     <string name="uninstall_update_title" msgid="824411791011583031">"Desinstalar atualização"</string>
     <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> é parte do seguinte app:"</string>
-    <string name="uninstall_application_text" msgid="3816830743706143980">"Quer desinstalar este app?"</string>
+    <string name="uninstall_application_text" msgid="3816830743706143980">"Você quer desinstalar este app?"</string>
     <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Quer desinstalar este app para "<b>"todos"</b>" os usuários? O aplicativo e os dados dele serão removidos para "<b>"todos"</b>" os usuários do dispositivo."</string>
     <string name="uninstall_application_text_user" msgid="498072714173920526">"Quer desinstalar este app para o usuário <xliff:g id="USERNAME">%1$s</xliff:g>?"</string>
     <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Você quer desinstalar esse app do seu perfil de trabalho?"</string>
diff --git a/packages/PrintSpooler/res/values-or/strings.xml b/packages/PrintSpooler/res/values-or/strings.xml
index 6f215d3..dd29700 100644
--- a/packages/PrintSpooler/res/values-or/strings.xml
+++ b/packages/PrintSpooler/res/values-or/strings.xml
@@ -47,7 +47,7 @@
     <string name="savetopdf_button" msgid="2976186791686924743">"PDFରେ ସେଭ୍‍ କରନ୍ତୁ"</string>
     <string name="print_options_expanded" msgid="6944679157471691859">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ବଡ଼ କରାଯାଇଛି"</string>
     <string name="print_options_collapsed" msgid="7455930445670414332">"ପ୍ରିଣ୍ଟ ବିକଳ୍ପକୁ ଛୋଟ କରାଯାଇଛି"</string>
-    <string name="search" msgid="5421724265322228497">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+    <string name="search" msgid="5421724265322228497">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
     <string name="all_printers_label" msgid="3178848870161526399">"ସମସ୍ତ ପ୍ରିଣ୍ଟର୍‌"</string>
     <string name="add_print_service_label" msgid="5356702546188981940">"ସେବା ଯୋଗ କରନ୍ତୁ"</string>
     <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ସର୍ଚ୍ଚ ବକ୍ସ ଦେଖାଯାଇଛି"</string>
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/attrs.xml b/packages/SettingsLib/IllustrationPreference/res/values/attrs.xml
new file mode 100644
index 0000000..141886c
--- /dev/null
+++ b/packages/SettingsLib/IllustrationPreference/res/values/attrs.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <declare-styleable name="IllustrationPreference">
+        <attr name="dynamicColor" format="boolean" />
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index 1592094..3b90275 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -61,6 +61,8 @@
     private View mMiddleGroundView;
     private OnBindListener mOnBindListener;
 
+    private boolean mLottieDynamicColor;
+
     /**
      * Interface to listen in on when {@link #onBindViewHolder(PreferenceViewHolder)} occurs.
      */
@@ -146,6 +148,10 @@
             ColorUtils.applyDynamicColors(getContext(), illustrationView);
         }
 
+        if (mLottieDynamicColor) {
+            LottieColorUtils.applyDynamicColors(getContext(), illustrationView);
+        }
+
         if (mOnBindListener != null) {
             mOnBindListener.onBind(illustrationView);
         }
@@ -262,6 +268,21 @@
         }
     }
 
+    /**
+     * Sets the lottie illustration apply dynamic color.
+     */
+    public void applyDynamicColor() {
+        mLottieDynamicColor = true;
+        notifyChanged();
+    }
+
+    /**
+     * Return if the lottie illustration apply dynamic color or not.
+     */
+    public boolean isApplyDynamicColor() {
+        return mLottieDynamicColor;
+    }
+
     private void resetImageResourceCache() {
         mImageDrawable = null;
         mImageUri = null;
@@ -403,9 +424,15 @@
 
         mIsAutoScale = false;
         if (attrs != null) {
-            final TypedArray a = context.obtainStyledAttributes(attrs,
+            TypedArray a = context.obtainStyledAttributes(attrs,
                     R.styleable.LottieAnimationView, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
             mImageResId = a.getResourceId(R.styleable.LottieAnimationView_lottie_rawRes, 0);
+
+            a = context.obtainStyledAttributes(attrs,
+                    R.styleable.IllustrationPreference, 0 /*defStyleAttr*/, 0 /*defStyleRes*/);
+            mLottieDynamicColor = a.getBoolean(R.styleable.IllustrationPreference_dynamicColor,
+                    false);
+
             a.recycle();
         }
     }
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/res/values-ky/strings.xml b/packages/SettingsLib/SelectorWithWidgetPreference/res/values-ky/strings.xml
index f4a893a..bcd6784b 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/res/values-ky/strings.xml
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/res/values-ky/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="settings_label" msgid="5948970810295631236">"Жөндөөлөр"</string>
+    <string name="settings_label" msgid="5948970810295631236">"Параметрлер"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 7377c3e..44c3d41 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -319,8 +319,8 @@
     <string name="select_logd_size_dialog_title" msgid="2105401994681013578">"প্ৰতিটো লগ বাফাৰত ল\'গাৰৰ আকাৰ বাছনি কৰক"</string>
     <string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"লগাৰৰ স্থায়ী ষ্ট’ৰেজৰ বস্তুবোৰ মচিবনে?"</string>
     <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"পাৰ্ছিছটেণ্ট লগাৰ ব্যৱহাৰ কৰ নিৰীক্ষণ নকৰাৰ সময়ত, আমি আপোনাৰ ডিভাইচত থকা লগাৰ ডেটা নিৱাসীক মচা দৰকাৰ।"</string>
-    <string name="select_logpersist_title" msgid="447071974007104196">"ডিভাইচটোত লগাৰৰ ডেটা নিৰবচ্ছিন্নভাৱে সঞ্চয় কৰক"</string>
-    <string name="select_logpersist_dialog_title" msgid="7745193591195485594">"ডিভাইচত স্থায়ীভাৱে সঞ্চয় কৰিবলৈ লগ বাফাৰবোৰ বাছনি কৰক"</string>
+    <string name="select_logpersist_title" msgid="447071974007104196">"ডিভাইচটোত লগাৰৰ ডেটা নিৰবচ্ছিন্নভাৱে ষ্ট’ৰ কৰক"</string>
+    <string name="select_logpersist_dialog_title" msgid="7745193591195485594">"ডিভাইচত স্থায়ীভাৱে ষ্ট’ৰ কৰিবলৈ লগ বাফাৰবোৰ বাছনি কৰক"</string>
     <string name="select_usb_configuration_title" msgid="6339801314922294586">"ইউএছবি কনফিগাৰেশ্বন বাছনি কৰক"</string>
     <string name="select_usb_configuration_dialog_title" msgid="3579567144722589237">"ইউএছবি কনফিগাৰেশ্বন বাছনি কৰক"</string>
     <string name="allow_mock_location" msgid="2102650981552527884">"নকল অৱস্থানৰ অনুমতি দিয়ক"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 518161a..9450927 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -183,7 +183,7 @@
     <string name="running_process_item_user_label" msgid="3988506293099805796">"Օգտատեր՝ <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
     <string name="launch_defaults_some" msgid="3631650616557252926">"Որոշ կանխադրված կարգավորումներ կան"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"Կանխադրված կարգավորումներ չկան"</string>
-    <string name="tts_settings" msgid="8130616705989351312">"Տեքստի հնչեցման կարգավորումներ"</string>
+    <string name="tts_settings" msgid="8130616705989351312">"Տեքստի հնչեցման կարգա­վորումներ"</string>
     <string name="tts_settings_title" msgid="7602210956640483039">"Տեքստի հնչեցում"</string>
     <string name="tts_default_rate_title" msgid="3964187817364304022">"Խոսքի արագությունը"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"Տեքստի արտասանման արագությունը"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index a904fb2..05da8008 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -183,7 +183,7 @@
     <string name="running_process_item_user_label" msgid="3988506293099805796">"Колдонуучу: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
     <string name="launch_defaults_some" msgid="3631650616557252926">"Айрым демейки параметрлер туураланды"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"Демейки маанилер коюлган жок"</string>
-    <string name="tts_settings" msgid="8130616705989351312">"Кеп синтезаторунун жөндөөлөрү"</string>
+    <string name="tts_settings" msgid="8130616705989351312">"Кеп синтезаторунун параметрлери"</string>
     <string name="tts_settings_title" msgid="7602210956640483039">"Кеп синтезатору"</string>
     <string name="tts_default_rate_title" msgid="3964187817364304022">"Кеп ылдамдыгы"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"Текст айтылчу ылдамдык"</string>
@@ -229,9 +229,9 @@
     <string name="development_settings_enable" msgid="4285094651288242183">"Иштеп чыгуучунун параметрлерин иштетүү"</string>
     <string name="development_settings_summary" msgid="8718917813868735095">"Колдонмо өндүрүү мүмкүнчүлүктөрүн орнотуу"</string>
     <string name="development_settings_not_available" msgid="355070198089140951">"Бул колдонуучуга өнүктүүрүүчү мүмкүнчүлүктөрү берилген эмес."</string>
-    <string name="vpn_settings_not_available" msgid="2894137119965668920">"Бул колдонуучу VPN жөндөөлөрүн колдоно албайт"</string>
-    <string name="tethering_settings_not_available" msgid="266821736434699780">"Бул колдонуучу модем режиминин жөндөөлөрүн өзгөртө албайт"</string>
-    <string name="apn_settings_not_available" msgid="1147111671403342300">"Бул колдонуучу мүмкүндүк алуу түйүнүнүн аталышынын жөндөөлөрүн колдоно албайт"</string>
+    <string name="vpn_settings_not_available" msgid="2894137119965668920">"Бул колдонуучу VPN параметрлерин колдоно албайт"</string>
+    <string name="tethering_settings_not_available" msgid="266821736434699780">"Бул колдонуучу модем режиминин параметрлерин өзгөртө албайт"</string>
+    <string name="apn_settings_not_available" msgid="1147111671403342300">"Бул колдонуучу мүмкүндүк алуу түйүнүнүн аталышынын параметрлерин колдоно албайт"</string>
     <string name="enable_adb" msgid="8072776357237289039">"USB аркылуу мүчүлүштүктөрдү аныктоо"</string>
     <string name="enable_adb_summary" msgid="3711526030096574316">"USB компьютерге сайылганда мүчүлүштүктөрдү оңдоо режими иштейт"</string>
     <string name="clear_adb_keys" msgid="3010148733140369917">"USB аркылуу мүчүлүштүктөрдү аныктоо уруксатын артка кайтаруу"</string>
@@ -332,7 +332,7 @@
     <string name="adb_warning_message" msgid="8145270656419669221">"USB-жөндөө - өндүрүү максатында гана  түзүлгөн. Аны компүтериңиз менен түзмөгүңүздүн ортосунда берилиштерди алмашуу, түзмөгүңүзгө колдонмолорду эскертүүсүз орнотуу жана лог берилиштерин окуу үчүн колдонсоңуз болот."</string>
     <string name="adbwifi_warning_title" msgid="727104571653031865">"Мүчүлүштүктөрдү Wi-Fi аркылуу оңдоого уруксат бересизби?"</string>
     <string name="adbwifi_warning_message" msgid="8005936574322702388">"Мүчүлүштүктөрдү Wi-Fi аркылуу аныктоо – өндүрүү максатында гана түзүлгөн. Аны компьютериңиз менен түзмөгүңүздүн ортосунда маалыматты алмашуу, колдонмолорду түзмөгүңүзгө эскертүүсүз орнотуу жана маалыматтар таржымалын окуу үчүн колдонсоңуз болот."</string>
-    <string name="adb_keys_warning_message" msgid="2968555274488101220">"Сиз мурун USB жөндөөлөрүнө уруксат берген бардык компүтерлердин жеткиси жокко чыгарылсынбы?"</string>
+    <string name="adb_keys_warning_message" msgid="2968555274488101220">"Сиз мурун USB параметрлерине уруксат берген бардык компүтерлердин жеткиси жокко чыгарылсынбы?"</string>
     <string name="dev_settings_warning_title" msgid="8251234890169074553">"Параметрлерди өзгөртүү"</string>
     <string name="dev_settings_warning_message" msgid="37741686486073668">"Бул орнотуулар өндүрүүчүлөр үчүн гана берилген. Булар түзмөгүңүздүн колдонмолорун бузулушуна же туура эмес иштешине алып келиши мүмкүн."</string>
     <string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Орнотулуучу колдонмону текшерүү"</string>
@@ -430,7 +430,7 @@
     <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Иштеген жок. Күйгүзүү үчүн басып коюңуз."</string>
     <string name="inactive_app_active_summary" msgid="8047630990208722344">"Иштеп турат. Өчүрүү үчүн басып коюңуз."</string>
     <string name="standby_bucket_summary" msgid="5128193447550429600">"Көшүү режиминдеги колдонмонун абалы:<xliff:g id="BUCKET"> %s</xliff:g>"</string>
-    <string name="transcode_settings_title" msgid="2581975870429850549">"Медиа файлдарды транскоддоо жөндөөлөрү"</string>
+    <string name="transcode_settings_title" msgid="2581975870429850549">"Медиа файлдарды транскоддоо параметрлери"</string>
     <string name="transcode_user_control" msgid="6176368544817731314">"Демейки жүргүзүлгөн транскоддоону өзгөртүп коюу"</string>
     <string name="transcode_enable_all" msgid="2411165920039166710">"Транскоддоо жүргүзүүнү иштетүү"</string>
     <string name="transcode_default" msgid="3784803084573509491">"Колдонмолордо заманбап форматтар колдоого алынат"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 91ab024..c75c40fa 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -183,7 +183,7 @@
     <string name="running_process_item_user_label" msgid="3988506293099805796">"အသုံးပြုသူ- <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
     <string name="launch_defaults_some" msgid="3631650616557252926">"မူရင်းအချို့ သတ်မှတ်ပြီး"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"မူရင်း သတ်မှတ်မထားပါ။"</string>
-    <string name="tts_settings" msgid="8130616705989351312">"စာ-မှ-စကားပြောင်းခြင်း ဆက်တင်များ"</string>
+    <string name="tts_settings" msgid="8130616705989351312">"စာ-မှ-စကား ဆက်တင်များ"</string>
     <string name="tts_settings_title" msgid="7602210956640483039">"စာ-မှ-စကားသို့ အထွက်"</string>
     <string name="tts_default_rate_title" msgid="3964187817364304022">"စကားပြောနှုန်း"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"စာတမ်းအားပြောဆိုသော အမြန်နှုန်း"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/arrays.xml b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
index 66aaa56b..b69fd53 100644
--- a/packages/SettingsLib/res/values-zh-rTW/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/arrays.xml
@@ -247,7 +247,7 @@
   </string-array>
   <string-array name="track_frame_time_entries">
     <item msgid="634406443901014984">"關閉"</item>
-    <item msgid="1288760936356000927">"在螢幕上以列顯示"</item>
+    <item msgid="1288760936356000927">"在螢幕上顯示長條圖"</item>
     <item msgid="5023908510820531131">"在「<xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g>」指令中"</item>
   </string-array>
   <string-array name="debug_hw_overdraw_entries">
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index f557517..50d2921 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -351,7 +351,7 @@
     <string name="debug_app_set" msgid="6599535090477753651">"偵錯應用程式:<xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="select_application" msgid="2543228890535466325">"選取應用程式"</string>
     <string name="no_application" msgid="9038334538870247690">"無"</string>
-    <string name="wait_for_debugger" msgid="7461199843335409809">"等待偵錯程式"</string>
+    <string name="wait_for_debugger" msgid="7461199843335409809">"等待偵錯工具"</string>
     <string name="wait_for_debugger_summary" msgid="6846330006113363286">"執行受偵錯的應用程式之前,先等待偵錯程序附加完成"</string>
     <string name="debug_input_category" msgid="7349460906970849771">"輸入"</string>
     <string name="debug_drawing_category" msgid="5066171112313666619">"繪圖"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
index 44a37f4..d4d2b48 100644
--- a/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
 import android.os.AsyncTask;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -32,6 +33,9 @@
 import com.android.settingslib.R;
 
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
 
 /**
  * Utility methods for working with display density.
@@ -70,120 +74,169 @@
      */
     private static final int MIN_DIMENSION_DP = 320;
 
-    private final String[] mEntries;
-    private final int[] mValues;
+    private static final Predicate<DisplayInfo> INTERNAL_ONLY =
+            (info) -> info.type == Display.TYPE_INTERNAL;
 
-    private final int mDefaultDensity;
-    private final int mCurrentIndex;
+    private final Predicate<DisplayInfo> mPredicate;
+
+    private final DisplayManager mDisplayManager;
+
+    /**
+     * The text description of the density values of the default display.
+     */
+    private String[] mDefaultDisplayDensityEntries;
+
+    /**
+     * The density values of the default display.
+     */
+    private int[] mDefaultDisplayDensityValues;
+
+    /**
+     * The density values, indexed by display unique ID.
+     */
+    private final Map<String, int[]> mValuesPerDisplay = new HashMap();
+
+    private int mDefaultDensityForDefaultDisplay;
+    private int mCurrentIndex = -1;
 
     public DisplayDensityUtils(Context context) {
-        final int defaultDensity = DisplayDensityUtils.getDefaultDisplayDensity(
-                Display.DEFAULT_DISPLAY);
-        if (defaultDensity <= 0) {
-            mEntries = null;
-            mValues = null;
-            mDefaultDensity = 0;
-            mCurrentIndex = -1;
-            return;
-        }
+        this(context, INTERNAL_ONLY);
+    }
 
-        final Resources res = context.getResources();
-        DisplayInfo info = new DisplayInfo();
-        context.getDisplayNoVerify().getDisplayInfo(info);
+    /**
+     * Creates an instance that stores the density values for the displays that satisfy
+     * the predicate.
+     * @param context The context
+     * @param predicate Determines what displays the density should be set for. The default display
+     *                  must satisfy this predicate.
+     */
+    public DisplayDensityUtils(Context context, Predicate predicate) {
+        mPredicate = predicate;
+        mDisplayManager = context.getSystemService(DisplayManager.class);
 
-        final int currentDensity = info.logicalDensityDpi;
-        int currentDensityIndex = -1;
-
-        // Compute number of "larger" and "smaller" scales for this display.
-        final int minDimensionPx = Math.min(info.logicalWidth, info.logicalHeight);
-        final int maxDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
-        final float maxScaleDimen = context.getResources().getFraction(
-                R.fraction.display_density_max_scale, 1, 1);
-        final float maxScale = Math.min(maxScaleDimen, maxDensity / (float) defaultDensity);
-        final float minScale = context.getResources().getFraction(
-                R.fraction.display_density_min_scale, 1, 1);
-        final float minScaleInterval = context.getResources().getFraction(
-                R.fraction.display_density_min_scale_interval, 1, 1);
-        final int numLarger = (int) MathUtils.constrain((maxScale - 1) / minScaleInterval,
-                0, SUMMARIES_LARGER.length);
-        final int numSmaller = (int) MathUtils.constrain((1 - minScale) / minScaleInterval,
-                0, SUMMARIES_SMALLER.length);
-
-        String[] entries = new String[1 + numSmaller + numLarger];
-        int[] values = new int[entries.length];
-        int curIndex = 0;
-
-        if (numSmaller > 0) {
-            final float interval = (1 - minScale) / numSmaller;
-            for (int i = numSmaller - 1; i >= 0; i--) {
-                // Round down to a multiple of 2 by truncating the low bit.
-                final int density = ((int) (defaultDensity * (1 - (i + 1) * interval))) & ~1;
-                if (currentDensity == density) {
-                    currentDensityIndex = curIndex;
-                }
-                entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]);
-                values[curIndex] = density;
-                curIndex++;
+        for (Display display : mDisplayManager.getDisplays(
+                DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
+            DisplayInfo info = new DisplayInfo();
+            if (!display.getDisplayInfo(info)) {
+                Log.w(LOG_TAG, "Cannot fetch display info for display " + display.getDisplayId());
+                continue;
             }
-        }
-
-        if (currentDensity == defaultDensity) {
-            currentDensityIndex = curIndex;
-        }
-        values[curIndex] = defaultDensity;
-        entries[curIndex] = res.getString(SUMMARY_DEFAULT);
-        curIndex++;
-
-        if (numLarger > 0) {
-            final float interval = (maxScale - 1) / numLarger;
-            for (int i = 0; i < numLarger; i++) {
-                // Round down to a multiple of 2 by truncating the low bit.
-                final int density = ((int) (defaultDensity * (1 + (i + 1) * interval))) & ~1;
-                if (currentDensity == density) {
-                    currentDensityIndex = curIndex;
+            if (!mPredicate.test(info)) {
+                if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                    throw new IllegalArgumentException("Predicate must not filter out the default "
+                            + "display.");
                 }
-                values[curIndex] = density;
-                entries[curIndex] = res.getString(SUMMARIES_LARGER[i]);
-                curIndex++;
+                continue;
             }
+
+            final int defaultDensity = DisplayDensityUtils.getDefaultDensityForDisplay(
+                    display.getDisplayId());
+            if (defaultDensity <= 0) {
+                Log.w(LOG_TAG, "Cannot fetch default density for display "
+                        + display.getDisplayId());
+                continue;
+            }
+
+            final Resources res = context.getResources();
+
+            final int currentDensity = info.logicalDensityDpi;
+            int currentDensityIndex = -1;
+
+            // Compute number of "larger" and "smaller" scales for this display.
+            final int minDimensionPx = Math.min(info.logicalWidth, info.logicalHeight);
+            final int maxDensity =
+                    DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / MIN_DIMENSION_DP;
+            final float maxScaleDimen = context.getResources().getFraction(
+                    R.fraction.display_density_max_scale, 1, 1);
+            final float maxScale = Math.min(maxScaleDimen, maxDensity / (float) defaultDensity);
+            final float minScale = context.getResources().getFraction(
+                    R.fraction.display_density_min_scale, 1, 1);
+            final float minScaleInterval = context.getResources().getFraction(
+                    R.fraction.display_density_min_scale_interval, 1, 1);
+            final int numLarger = (int) MathUtils.constrain((maxScale - 1) / minScaleInterval,
+                    0, SUMMARIES_LARGER.length);
+            final int numSmaller = (int) MathUtils.constrain((1 - minScale) / minScaleInterval,
+                    0, SUMMARIES_SMALLER.length);
+
+            String[] entries = new String[1 + numSmaller + numLarger];
+            int[] values = new int[entries.length];
+            int curIndex = 0;
+
+            if (numSmaller > 0) {
+                final float interval = (1 - minScale) / numSmaller;
+                for (int i = numSmaller - 1; i >= 0; i--) {
+                    // Round down to a multiple of 2 by truncating the low bit.
+                    final int density = ((int) (defaultDensity * (1 - (i + 1) * interval))) & ~1;
+                    if (currentDensity == density) {
+                        currentDensityIndex = curIndex;
+                    }
+                    entries[curIndex] = res.getString(SUMMARIES_SMALLER[i]);
+                    values[curIndex] = density;
+                    curIndex++;
+                }
+            }
+
+            if (currentDensity == defaultDensity) {
+                currentDensityIndex = curIndex;
+            }
+            values[curIndex] = defaultDensity;
+            entries[curIndex] = res.getString(SUMMARY_DEFAULT);
+            curIndex++;
+
+            if (numLarger > 0) {
+                final float interval = (maxScale - 1) / numLarger;
+                for (int i = 0; i < numLarger; i++) {
+                    // Round down to a multiple of 2 by truncating the low bit.
+                    final int density = ((int) (defaultDensity * (1 + (i + 1) * interval))) & ~1;
+                    if (currentDensity == density) {
+                        currentDensityIndex = curIndex;
+                    }
+                    values[curIndex] = density;
+                    entries[curIndex] = res.getString(SUMMARIES_LARGER[i]);
+                    curIndex++;
+                }
+            }
+
+            final int displayIndex;
+            if (currentDensityIndex >= 0) {
+                displayIndex = currentDensityIndex;
+            } else {
+                // We don't understand the current density. Must have been set by
+                // someone else. Make room for another entry...
+                int newLength = values.length + 1;
+                values = Arrays.copyOf(values, newLength);
+                values[curIndex] = currentDensity;
+
+                entries = Arrays.copyOf(entries, newLength);
+                entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity);
+
+                displayIndex = curIndex;
+            }
+
+            if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                mDefaultDensityForDefaultDisplay = defaultDensity;
+                mCurrentIndex = displayIndex;
+                mDefaultDisplayDensityEntries = entries;
+                mDefaultDisplayDensityValues = values;
+            }
+            mValuesPerDisplay.put(info.uniqueId, values);
         }
-
-        final int displayIndex;
-        if (currentDensityIndex >= 0) {
-            displayIndex = currentDensityIndex;
-        } else {
-            // We don't understand the current density. Must have been set by
-            // someone else. Make room for another entry...
-            int newLength = values.length + 1;
-            values = Arrays.copyOf(values, newLength);
-            values[curIndex] = currentDensity;
-
-            entries = Arrays.copyOf(entries, newLength);
-            entries[curIndex] = res.getString(SUMMARY_CUSTOM, currentDensity);
-
-            displayIndex = curIndex;
-        }
-
-        mDefaultDensity = defaultDensity;
-        mCurrentIndex = displayIndex;
-        mEntries = entries;
-        mValues = values;
     }
 
-    public String[] getEntries() {
-        return mEntries;
+    public String[] getDefaultDisplayDensityEntries() {
+        return mDefaultDisplayDensityEntries;
     }
 
-    public int[] getValues() {
-        return mValues;
+    public int[] getDefaultDisplayDensityValues() {
+        return mDefaultDisplayDensityValues;
     }
 
-    public int getCurrentIndex() {
+    public int getCurrentIndexForDefaultDisplay() {
         return mCurrentIndex;
     }
 
-    public int getDefaultDensity() {
-        return mDefaultDensity;
+    public int getDefaultDensityForDefaultDisplay() {
+        return mDefaultDensityForDefaultDisplay;
     }
 
     /**
@@ -193,7 +246,7 @@
      * @return the default density of the specified display, or {@code -1} if
      *         the display does not exist or the density could not be obtained
      */
-    private static int getDefaultDisplayDensity(int displayId) {
+    private static int getDefaultDensityForDisplay(int displayId) {
        try {
            final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
            return wm.getInitialDisplayDensity(displayId);
@@ -203,19 +256,31 @@
     }
 
     /**
-     * Asynchronously applies display density changes to the specified display.
+     * Asynchronously applies display density changes to the displays that satisfy the predicate.
      * <p>
      * The change will be applied to the user specified by the value of
      * {@link UserHandle#myUserId()} at the time the method is called.
-     *
-     * @param displayId the identifier of the display to modify
      */
-    public static void clearForcedDisplayDensity(final int displayId) {
+    public void clearForcedDisplayDensity() {
         final int userId = UserHandle.myUserId();
         AsyncTask.execute(() -> {
             try {
-                final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-                wm.clearForcedDisplayDensityForUser(displayId, userId);
+                for (Display display : mDisplayManager.getDisplays(
+                        DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
+                    int displayId = display.getDisplayId();
+                    DisplayInfo info = new DisplayInfo();
+                    if (!display.getDisplayInfo(info)) {
+                        Log.w(LOG_TAG, "Unable to clear forced display density setting "
+                                + "for display " + displayId);
+                        continue;
+                    }
+                    if (!mPredicate.test(info)) {
+                        continue;
+                    }
+
+                    final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+                    wm.clearForcedDisplayDensityForUser(displayId, userId);
+                }
             } catch (RemoteException exc) {
                 Log.w(LOG_TAG, "Unable to clear forced display density setting");
             }
@@ -223,20 +288,39 @@
     }
 
     /**
-     * Asynchronously applies display density changes to the specified display.
+     * Asynchronously applies display density changes to the displays that satisfy the predicate.
      * <p>
      * The change will be applied to the user specified by the value of
      * {@link UserHandle#myUserId()} at the time the method is called.
      *
-     * @param displayId the identifier of the display to modify
-     * @param density the density to force for the specified display
+     * @param index The index of the density value
      */
-    public static void setForcedDisplayDensity(final int displayId, final int density) {
+    public void setForcedDisplayDensity(final int index) {
         final int userId = UserHandle.myUserId();
         AsyncTask.execute(() -> {
             try {
-                final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-                wm.setForcedDisplayDensityForUser(displayId, density, userId);
+                for (Display display : mDisplayManager.getDisplays(
+                        DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) {
+                    int displayId = display.getDisplayId();
+                    DisplayInfo info = new DisplayInfo();
+                    if (!display.getDisplayInfo(info)) {
+                        Log.w(LOG_TAG, "Unable to save forced display density setting "
+                                + "for display " + displayId);
+                        continue;
+                    }
+                    if (!mPredicate.test(info)) {
+                        continue;
+                    }
+                    if (!mValuesPerDisplay.containsKey(info.uniqueId)) {
+                        Log.w(LOG_TAG, "Unable to save forced display density setting "
+                                + "for display " + info.uniqueId);
+                        continue;
+                    }
+
+                    final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+                    wm.setForcedDisplayDensityForUser(displayId,
+                            mValuesPerDisplay.get(info.uniqueId)[index], userId);
+                }
             } catch (RemoteException exc) {
                 Log.w(LOG_TAG, "Unable to save forced display density setting");
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index d6586db..d9d7cc9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -185,6 +185,24 @@
     public abstract String getId();
 
     /**
+     * Get disabled reason of device
+     *
+     * @return disabled reason of device
+     */
+    public int getDisableReason() {
+        return -1;
+    }
+
+    /**
+     * Checks if device is has disabled reason
+     *
+     * @return true if device has disabled reason
+     */
+    public boolean hasDisabledReason() {
+        return false;
+    }
+
+    /**
      * Checks if device is suggested device from application
      *
      * @return true if device is suggested device
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
index 103512d..21e119a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
@@ -215,4 +215,20 @@
 
         assertThat(mOnBindListenerAnimationView).isNull();
     }
+
+    @Test
+    public void onBindViewHolder_default_shouldNotApplyDynamicColor() {
+        mPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mPreference.isApplyDynamicColor()).isFalse();
+    }
+
+    @Test
+    public void onBindViewHolder_applyDynamicColor_shouldReturnTrue() {
+        mPreference.applyDynamicColor();
+
+        mPreference.onBindViewHolder(mViewHolder);
+
+        assertThat(mPreference.isApplyDynamicColor()).isTrue();
+    }
 }
diff --git a/packages/SettingsProvider/res/values-ky/strings.xml b/packages/SettingsProvider/res/values-ky/strings.xml
index 8058b4d..7ab6582 100644
--- a/packages/SettingsProvider/res/values-ky/strings.xml
+++ b/packages/SettingsProvider/res/values-ky/strings.xml
@@ -20,6 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Жөндөөлөрдү сактоо"</string>
-    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Байланыш түйүнү  жөндөөлөрү өзгөрдү"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Байланыш түйүнү  параметрлери өзгөрдү"</string>
     <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Чоо-жайын билүү үчүн басыңыз"</string>
 </resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
new file mode 100644
index 0000000..bdd4869
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/LargeScreenSettings.java
@@ -0,0 +1,53 @@
+/*
+ * 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 android.provider.settings.backup;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+/** Settings that should not be restored when target device is a large screen
+ *  i.e. tablets and foldables in unfolded state
+ */
+public class LargeScreenSettings {
+    private static final float LARGE_SCREEN_MIN_DPS = 600;
+    private static final String LARGE_SCREEN_DO_NOT_RESTORE = "accelerometer_rotation";
+
+   /**
+    * Autorotation setting should not be restored when the target device is a large screen.
+    * (b/243489549)
+    */
+    public static boolean doNotRestoreIfLargeScreenSetting(String key, Context context) {
+        return isLargeScreen(context) && LARGE_SCREEN_DO_NOT_RESTORE.equals(key);
+    }
+
+    // copied from systemui/shared/...Utilities.java
+    // since we don't want to add compile time dependency on sys ui package
+    private static boolean isLargeScreen(Context context) {
+        final WindowManager windowManager = context.getSystemService(WindowManager.class);
+        final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
+        float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
+                context.getResources().getConfiguration().densityDpi);
+        return smallestWidth >= LARGE_SCREEN_MIN_DPS;
+    }
+
+    private static float dpiFromPx(float size, int densityDpi) {
+        float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+        return (size / densityRatio);
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index d3afccc..d0055d7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -37,6 +37,7 @@
 import android.provider.Settings;
 import android.provider.settings.backup.DeviceSpecificSettings;
 import android.provider.settings.backup.GlobalSettings;
+import android.provider.settings.backup.LargeScreenSettings;
 import android.provider.settings.backup.SecureSettings;
 import android.provider.settings.backup.SystemSettings;
 import android.provider.settings.validators.GlobalSettingsValidators;
@@ -812,6 +813,12 @@
                 continue;
             }
 
+            if (LargeScreenSettings.doNotRestoreIfLargeScreenSetting(key, getBaseContext())) {
+                Log.i(TAG, "Skipping restore for setting " + key + " as the target device "
+                        + "is a large screen (i.e tablet or foldable in unfolded state)");
+                continue;
+            }
+
             String value = null;
             boolean hasValueToRestore = false;
             if (cachedEntries.indexOfKey(key) >= 0) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 3b862ff..3915012 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -356,6 +356,9 @@
     <!-- Permission needed to test wallpaper dimming -->
     <uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
 
+    <!-- Permission needed to test wallpaper read methods -->
+    <uses-permission android:name="android.permission.READ_WALLPAPER_INTERNAL" />
+
     <!-- Permission required to test ContentResolver caching. -->
     <uses-permission android:name="android.permission.CACHE_CONTENT" />
 
diff --git a/packages/Shell/res/values-mk/strings.xml b/packages/Shell/res/values-mk/strings.xml
index 0856198..93d4d52 100644
--- a/packages/Shell/res/values-mk/strings.xml
+++ b/packages/Shell/res/values-mk/strings.xml
@@ -37,7 +37,7 @@
     <string name="bugreport_info_action" msgid="2158204228510576227">"Детали"</string>
     <string name="bugreport_screenshot_action" msgid="8677781721940614995">"Слика од екранот"</string>
     <string name="bugreport_screenshot_taken" msgid="5684211273096253120">"Успешно е направена слика од екранот."</string>
-    <string name="bugreport_screenshot_failed" msgid="5853049140806834601">"Не може да се направи слика од екранот."</string>
+    <string name="bugreport_screenshot_failed" msgid="5853049140806834601">"Не може да се зачува слика од екранот."</string>
     <string name="bugreport_info_dialog_title" msgid="1355948594292983332">"Детали за извештајот за грешки <xliff:g id="ID">#%d</xliff:g>"</string>
     <string name="bugreport_info_name" msgid="4414036021935139527">"Име на датотека"</string>
     <string name="bugreport_info_title" msgid="2306030793918239804">"Наслов на грешката"</string>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 75c92e0..e96aead5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -663,6 +663,21 @@
             android:excludeFromRecents="true"
             android:exported="true" />
 
+        <!-- started from Telecomm(CallsManager) -->
+        <activity
+            android:name=".telephony.ui.activity.SwitchToManagedProfileForCallActivity"
+            android:excludeFromRecents="true"
+            android:exported="true"
+            android:finishOnCloseSystemDialogs="true"
+            android:permission="android.permission.MODIFY_PHONE_STATE"
+            android:theme="@style/Theme.SystemUI.Dialog.Alert">
+            <intent-filter>
+                <action android:name="android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="tel" />
+            </intent-filter>
+        </activity>
+
         <!-- platform logo easter egg activity -->
         <activity
             android:name=".DessertCase"
@@ -907,7 +922,7 @@
             android:excludeFromRecents="true"
             android:theme="@android:style/Theme.NoDisplay"
             android:label="@string/note_task_button_label"
-            android:icon="@drawable/ic_note_task_button">
+            android:icon="@drawable/ic_note_task_shortcut_widget">
 
             <intent-filter>
                 <action android:name="android.intent.action.CREATE_SHORTCUT" />
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index ee8d023..2910bba 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -5,46 +5,72 @@
 SystemUI is a persistent process that provides UI for the system but outside
 of the system_server process.
 
-The starting point for most of sysui code is a list of services that extend
-SystemUI that are started up by SystemUIApplication. These services then depend
-on some custom dependency injection provided by Dependency.
-
 Inputs directed at sysui (as opposed to general listeners) generally come in
 through IStatusBar. Outputs from sysui are through a variety of private APIs to
 the android platform all over.
 
 ## SystemUIApplication
 
-When SystemUIApplication starts up, it will start up the services listed in
-config_systemUIServiceComponents or config_systemUIServiceComponentsPerUser.
+When SystemUIApplication starts up, it instantiates a Dagger graph from which
+various pieces of the application are built.
 
-Each of these services extend SystemUI. SystemUI provides them with a Context
-and gives them callbacks for onConfigurationChanged (this historically was
-the main path for onConfigurationChanged, now also happens through
-ConfigurationController). They also receive a callback for onBootCompleted
+To support customization, SystemUIApplication relies on the AndroidManifest.xml
+having an `android.app.AppComponentFactory` specified. Specifically, it relies
+on an `AppComponentFactory` that subclases `SystemUIAppComponentFactoryBase`.
+Implementations of this abstract base class must override
+`#createSystemUIInitializer(Context)` which returns a `SystemUIInitializer`.
+`SystemUIInitializer` primary job in turn is to intialize and return the Dagger
+root component back to the `SystemUIApplication`.
+
+Writing a custom `SystemUIAppComponentFactoryBase` and `SystemUIInitializer`,
+should be enough for most implementations to stand up a customized Dagger
+graph, and launch a custom version of SystemUI.
+
+## Dagger / Dependency Injection
+
+See [dagger.md](docs/dagger.md) and https://dagger.dev/.
+
+## CoreStartable
+
+The starting point for most of SystemUI code is a list of classes that
+implement `CoreStartable` that are started up by SystemUIApplication.
+CoreStartables are like miniature services. They have their `#start` method
+called after being instantiated, and a reference to them is stored inside
+SystemUIApplication. They are in charge of their own behavior beyond this,
+registering and unregistering with the rest of the system as needed.
+
+`CoreStartable` also receives a callback for `#onBootCompleted`
 since these objects may be started before the device has finished booting.
 
-Each SystemUI service is expected to be a major part of system ui and the
-goal is to minimize communication between them. So in general they should be
-relatively silo'd.
+`CoreStartable` is an ideal place to add new features and functionality
+that does not belong directly under the umbrella of an existing feature.
+It is better to define a new `CoreStartable` than to stick unrelated
+initialization code together in catch-all methods.
 
-## Dependencies
+CoreStartables are tied to application startup via Dagger:
 
-The first SystemUI service that is started should always be Dependency.
-Dependency provides a static method for getting a hold of dependencies that
-have a lifecycle that spans sysui. Dependency has code for how to create all
-dependencies manually added. SystemUIFactory is also capable of
-adding/replacing these dependencies.
+```kotlin
+class FeatureStartable
+@Inject
+constructor(
+    /* ... */
+) : CoreStartable {
+    override fun start() {
+        // ...
+    }
+}
 
-Dependencies are lazily initialized, so if a Dependency is never referenced at
-runtime, it will never be created.
+@Module
+abstract class FeatureModule {
+    @Binds
+    @IntoMap
+    @ClassKey(FeatureStartable::class)
+    abstract fun bind(impl: FeatureStartable): CoreStartable
+}
+```
 
-If an instantiated dependency implements Dumpable it will be included in dumps
-of sysui (and bug reports), allowing it to include current state information.
-This is how \*Controllers dump state to bug reports.
-
-If an instantiated dependency implements ConfigurationChangeReceiver it will
-receive onConfigurationChange callbacks when the configuration changes.
+Including `FeatureModule` in the Dagger graph such as this will ensure that
+`FeatureStartable` gets constructed and that its `#start` method is called.
 
 ## IStatusBar
 
@@ -64,12 +90,6 @@
 This is generally used a shortcut to directly trigger CommandQueue rather than
 calling StatusManager and waiting for the call to come back to IStatusBar.
 
-## Default SystemUI services list
-
-### [com.android.systemui.Dependency](/packages/SystemUI/src/com/android/systemui/Dependency.java)
-
-Provides custom dependency injection.
-
 ### [com.android.systemui.util.NotificationChannels](/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java)
 
 Creates/initializes the channels sysui uses when posting notifications.
@@ -88,11 +108,11 @@
 Registers all the callbacks/listeners required to show the Volume dialog when
 it should be shown.
 
-### [com.android.systemui.status.phone.StatusBar](/packages/SystemUI/src/com/android/systemui/status/phone/StatusBar.java)
+### [com.android.systemui.status.phone.CentralSurfaces](/packages/SystemUI/src/com/android/systemui/status/phone/CentralSurfaces.java)
 
 This shows the UI for the status bar and the notification shade it contains.
 It also contains a significant amount of other UI that interacts with these
-surfaces (keyguard, AOD, etc.). StatusBar also contains a notification listener
+surfaces (keyguard, AOD, etc.). CentralSurfaces also contains a notification listener
 to receive notification callbacks.
 
 ### [com.android.systemui.usb.StorageNotification](/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java)
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index cd45b8ea..60bfdb2 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -16,6 +16,9 @@
         },
         {
             "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+        },
+        {
+            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
         }
       ]
     }
@@ -72,16 +75,16 @@
   // Curious where your @Scenario tests will run?
   //
   // @Ignore: nowhere
-  // @Staging or @FlakyTest: in staged-postsubmit, but not postsubmit or
-  // 	presubmit
+  // @FlakyTest: in staged-postsubmit, but not blocking postsubmit or
+  // presubmit
   // @Postsubmit: in postsubmit and staged-postsubmit, but not presubmit
   // none of the above: in presubmit, postsubmit, and staged-postsubmit
   //
-  // Therefore, please annotate new tests with @Staging, then with @Postsubmit
-  // once they're ready for postsubmit, then with neither once they're ready
-  // for presubmit.
+  // Ideally, please annotate new tests with @FlakyTest, then with @Postsubmit
+  // once they're ready for postsubmit as they will immediately block go/android-platinum,
+  // then with neither once they're ready for presubmit.
   //
-  // If you don't use @Staging or @Postsubmit, your new test will immediately
+  // If you don't use @Postsubmit, your new test will immediately
   // block presubmit, which is probably not what you want!
   "sysui-platinum-postsubmit": [
     {
@@ -98,6 +101,9 @@
         },
         {
             "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
         }
       ]
     }
@@ -130,6 +136,9 @@
         },
         {
             "include-filter": "android.platform.test.scenario.sysui"
+        },
+        {
+            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
         }
       ]
     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 8f70dcc..17a94b86 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -41,6 +41,7 @@
 import androidx.annotation.UiThread
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
+import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "ActivityLaunchAnimator"
@@ -301,10 +302,13 @@
 
     interface Callback {
         /** Whether we are currently on the keyguard or not. */
-        fun isOnKeyguard(): Boolean
+        @JvmDefault fun isOnKeyguard(): Boolean = false
 
         /** Hide the keyguard and animate using [runner]. */
-        fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner)
+        @JvmDefault
+        fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner) {
+            throw UnsupportedOperationException()
+        }
 
         /* Get the background color of [task]. */
         fun getBackgroundColor(task: TaskInfo): Int
@@ -335,13 +339,24 @@
              * Return a [Controller] that will animate and expand [view] into the opening window.
              *
              * Important: The view must be attached to a [ViewGroup] when calling this function and
-             * during the animation. For safety, this method will return null when it is not.
+             * during the animation. For safety, this method will return null when it is not. The
+             * view must also implement [LaunchableView], otherwise this method will throw.
              *
              * Note: The background of [view] should be a (rounded) rectangle so that it can be
              * properly animated.
              */
             @JvmStatic
             fun fromView(view: View, cujType: Int? = null): Controller? {
+                // Make sure the View we launch from implements LaunchableView to avoid visibility
+                // issues.
+                if (view !is LaunchableView) {
+                    throw IllegalArgumentException(
+                        "An ActivityLaunchAnimator.Controller was created from a View that does " +
+                            "not implement LaunchableView. This can lead to subtle bugs where the" +
+                            " visibility of the View we are launching from is not what we expected."
+                    )
+                }
+
                 if (view.parent !is ViewGroup) {
                     Log.e(
                         TAG,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
new file mode 100644
index 0000000..1c9dabb
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
@@ -0,0 +1,6 @@
+package com.android.systemui.animation
+
+interface AnimationFeatureFlags {
+    val isPredictiveBackQsDialogAnim: Boolean
+        get() = false
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 9a9236b..b8d78fb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,8 +33,14 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
 import android.widget.FrameLayout
+import android.window.OnBackInvokedDispatcher
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.jank.InteractionJankMonitor.CujType
+import com.android.systemui.animation.back.BackAnimationSpec
+import com.android.systemui.animation.back.applyTo
+import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
+import com.android.systemui.animation.back.onBackAnimationCallbackFrom
+import java.lang.IllegalArgumentException
 import kotlin.math.roundToInt
 
 private const val TAG = "DialogLaunchAnimator"
@@ -55,8 +61,9 @@
 constructor(
     private val callback: Callback,
     private val interactionJankMonitor: InteractionJankMonitor,
+    private val featureFlags: AnimationFeatureFlags,
     private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
-    private val isForTesting: Boolean = false
+    private val isForTesting: Boolean = false,
 ) {
     private companion object {
         private val TIMINGS = ActivityLaunchAnimator.TIMINGS
@@ -151,12 +158,23 @@
              * Create a [Controller] that can animate [source] to and from a dialog.
              *
              * Important: The view must be attached to a [ViewGroup] when calling this function and
-             * during the animation. For safety, this method will return null when it is not.
+             * during the animation. For safety, this method will return null when it is not. The
+             * view must also implement [LaunchableView], otherwise this method will throw.
              *
              * Note: The background of [view] should be a (rounded) rectangle so that it can be
              * properly animated.
              */
             fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
+                // Make sure the View we launch from implements LaunchableView to avoid visibility
+                // issues.
+                if (source !is LaunchableView) {
+                    throw IllegalArgumentException(
+                        "A DialogLaunchAnimator.Controller was created from a View that does not " +
+                            "implement LaunchableView. This can lead to subtle bugs where the " +
+                            "visibility of the View we are launching from is not what we expected."
+                    )
+                }
+
                 if (source.parent !is ViewGroup) {
                     Log.e(
                         TAG,
@@ -237,25 +255,12 @@
             openedDialogs.firstOrNull {
                 it.dialog.window.decorView.viewRootImpl == controller.viewRoot
             }
-        val animateFrom =
+        val controller =
             animatedParent?.dialogContentWithBackground?.let {
                 Controller.fromView(it, controller.cuj)
             }
                 ?: controller
 
-        if (animatedParent == null && animateFrom !is LaunchableView) {
-            // Make sure the View we launch from implements LaunchableView to avoid visibility
-            // issues. Given that we don't own dialog decorViews so we can't enforce it for launches
-            // from a dialog.
-            // TODO(b/243636422): Throw instead of logging to enforce this.
-            Log.w(
-                TAG,
-                "A dialog was launched from a View that does not implement LaunchableView. This " +
-                    "can lead to subtle bugs where the visibility of the View we are " +
-                    "launching from is not what we expected."
-            )
-        }
-
         // Make sure we don't run the launch animation from the same source twice at the same time.
         if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
             Log.e(
@@ -269,15 +274,16 @@
 
         val animatedDialog =
             AnimatedDialog(
-                launchAnimator,
-                callback,
-                interactionJankMonitor,
-                animateFrom,
+                launchAnimator = launchAnimator,
+                callback = callback,
+                interactionJankMonitor = interactionJankMonitor,
+                controller = controller,
                 onDialogDismissed = { openedDialogs.remove(it) },
                 dialog = dialog,
-                animateBackgroundBoundsChange,
-                animatedParent,
-                isForTesting,
+                animateBackgroundBoundsChange = animateBackgroundBoundsChange,
+                parentAnimatedDialog = animatedParent,
+                forceDisableSynchronization = isForTesting,
+                featureFlags = featureFlags,
             )
 
         openedDialogs.add(animatedDialog)
@@ -513,6 +519,7 @@
      * Whether synchronization should be disabled, which can be useful if we are running in a test.
      */
     private val forceDisableSynchronization: Boolean,
+    private val featureFlags: AnimationFeatureFlags,
 ) {
     /**
      * The DecorView of this dialog window.
@@ -601,10 +608,16 @@
                 }
 
                 // Animate that view with the background. Throw if we didn't find one, because
-                // otherwise
-                // it's not clear what we should animate.
+                // otherwise it's not clear what we should animate.
+                if (viewGroupWithBackground == null) {
+                    error("Unable to find ViewGroup with background")
+                }
+
+                if (viewGroupWithBackground !is LaunchableView) {
+                    error("The animated ViewGroup with background must implement LaunchableView")
+                }
+
                 viewGroupWithBackground
-                    ?: throw IllegalStateException("Unable to find ViewGroup with background")
             } else {
                 // We will make the dialog window (and therefore its DecorView) fullscreen to make
                 // it possible to animate outside its bounds.
@@ -627,7 +640,7 @@
                     FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
                 )
 
-                val dialogContentWithBackground = FrameLayout(dialog.context)
+                val dialogContentWithBackground = LaunchableFrameLayout(dialog.context)
                 dialogContentWithBackground.background = decorView.background
 
                 // Make the window background transparent. Note that setting the window (or
@@ -708,7 +721,10 @@
 
         // Make the background view invisible until we start the animation. We use the transition
         // visibility like GhostView does so that we don't mess up with the accessibility tree (see
-        // b/204944038#comment17).
+        // b/204944038#comment17). Given that this background implements LaunchableView, we call
+        // setShouldBlockVisibilityChanges() early so that the current visibility (VISIBLE) is
+        // restored at the end of the animation.
+        dialogContentWithBackground.setShouldBlockVisibilityChanges(true)
         dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
 
         // Make sure the dialog is visible instantly and does not do any window animation.
@@ -774,12 +790,45 @@
         // the dialog.
         dialog.setDismissOverride(this::onDialogDismissed)
 
+        if (featureFlags.isPredictiveBackQsDialogAnim) {
+            // TODO(b/265923095) Improve animations for QS dialogs on configuration change
+            registerOnBackInvokedCallback(targetView = dialogContentWithBackground)
+        }
+
         // Show the dialog.
         dialog.show()
-
         moveSourceDrawingToDialog()
     }
 
+    private fun registerOnBackInvokedCallback(targetView: View) {
+        val metrics = targetView.resources.displayMetrics
+
+        val onBackAnimationCallback =
+            onBackAnimationCallbackFrom(
+                backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(metrics),
+                displayMetrics = metrics, // TODO(b/265060720): We could remove this
+                onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) },
+                onBackInvoked = { dialog.dismiss() },
+            )
+
+        val dispatcher = dialog.onBackInvokedDispatcher
+        targetView.addOnAttachStateChangeListener(
+            object : View.OnAttachStateChangeListener {
+                override fun onViewAttachedToWindow(v: View) {
+                    dispatcher.registerOnBackInvokedCallback(
+                        OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                        onBackAnimationCallback
+                    )
+                }
+
+                override fun onViewDetachedFromWindow(v: View) {
+                    targetView.removeOnAttachStateChangeListener(this)
+                    dispatcher.unregisterOnBackInvokedCallback(onBackAnimationCallback)
+                }
+            }
+        )
+    }
+
     private fun moveSourceDrawingToDialog() {
         if (decorView.viewRootImpl == null) {
             // Make sure that we have access to the dialog view root to move the drawing to the
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 3d341af..2903288 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -33,9 +33,7 @@
 private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
 private const val FONT_ITALIC_DEFAULT_VALUE = 0f
 
-/**
- * Provide interpolation of two fonts by adjusting font variation settings.
- */
+/** Provide interpolation of two fonts by adjusting font variation settings. */
 class FontInterpolator {
 
     /**
@@ -61,11 +59,14 @@
         var index: Int,
         val sortedAxes: MutableList<FontVariationAxis>
     ) {
-        constructor(font: Font, axes: List<FontVariationAxis>) :
-                this(font.sourceIdentifier,
-                        font.ttcIndex,
-                        axes.toMutableList().apply { sortBy { it.tag } }
-                )
+        constructor(
+            font: Font,
+            axes: List<FontVariationAxis>
+        ) : this(
+            font.sourceIdentifier,
+            font.ttcIndex,
+            axes.toMutableList().apply { sortBy { it.tag } }
+        )
 
         fun set(font: Font, axes: List<FontVariationAxis>) {
             sourceId = font.sourceIdentifier
@@ -86,9 +87,7 @@
     private val tmpInterpKey = InterpKey(null, null, 0f)
     private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf())
 
-    /**
-     * Linear interpolate the font variation settings.
-     */
+    /** Linear interpolate the font variation settings. */
     fun lerp(start: Font, end: Font, progress: Float): Font {
         if (progress == 0f) {
             return start
@@ -115,27 +114,34 @@
         // this doesn't take much time since the variation axes is usually up to 5. If we need to
         // support more number of axes, we may want to preprocess the font and store the sorted axes
         // and also pre-fill the missing axes value with default value from 'fvar' table.
-        val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue ->
-            when (tag) {
-                // TODO: Good to parse 'fvar' table for retrieving default value.
-                TAG_WGHT -> adjustWeight(
-                        MathUtils.lerp(
+        val newAxes =
+            lerp(startAxes, endAxes) { tag, startValue, endValue ->
+                when (tag) {
+                    // TODO: Good to parse 'fvar' table for retrieving default value.
+                    TAG_WGHT ->
+                        adjustWeight(
+                            MathUtils.lerp(
                                 startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
                                 endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
-                                progress))
-                TAG_ITAL -> adjustItalic(
-                        MathUtils.lerp(
+                                progress
+                            )
+                        )
+                    TAG_ITAL ->
+                        adjustItalic(
+                            MathUtils.lerp(
                                 startValue ?: FONT_ITALIC_DEFAULT_VALUE,
                                 endValue ?: FONT_ITALIC_DEFAULT_VALUE,
-                                progress))
-                else -> {
-                    require(startValue != null && endValue != null) {
-                        "Unable to interpolate due to unknown default axes value : $tag"
+                                progress
+                            )
+                        )
+                    else -> {
+                        require(startValue != null && endValue != null) {
+                            "Unable to interpolate due to unknown default axes value : $tag"
+                        }
+                        MathUtils.lerp(startValue, endValue, progress)
                     }
-                    MathUtils.lerp(startValue, endValue, progress)
                 }
             }
-        }
 
         // Check if we already make font for this axes. This is typically happens if the animation
         // happens backward.
@@ -149,9 +155,7 @@
         // This is the first time to make the font for the axes. Build and store it to the cache.
         // Font.Builder#build won't throw IOException since creating fonts from existing fonts will
         // not do any IO work.
-        val newFont = Font.Builder(start)
-                .setFontVariationSettings(newAxes.toTypedArray())
-                .build()
+        val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
         interpCache[InterpKey(start, end, progress)] = newFont
         verFontCache[VarFontKey(start, newAxes)] = newFont
         return newFont
@@ -173,26 +177,28 @@
             val tagA = if (i < start.size) start[i].tag else null
             val tagB = if (j < end.size) end[j].tag else null
 
-            val comp = when {
-                tagA == null -> 1
-                tagB == null -> -1
-                else -> tagA.compareTo(tagB)
-            }
+            val comp =
+                when {
+                    tagA == null -> 1
+                    tagB == null -> -1
+                    else -> tagA.compareTo(tagB)
+                }
 
-            val axis = when {
-                comp == 0 -> {
-                    val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
-                    FontVariationAxis(tagA, v)
+            val axis =
+                when {
+                    comp == 0 -> {
+                        val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
+                        FontVariationAxis(tagA, v)
+                    }
+                    comp < 0 -> {
+                        val v = filter(tagA!!, start[i++].styleValue, null)
+                        FontVariationAxis(tagA, v)
+                    }
+                    else -> { // comp > 0
+                        val v = filter(tagB!!, null, end[j++].styleValue)
+                        FontVariationAxis(tagB, v)
+                    }
                 }
-                comp < 0 -> {
-                    val v = filter(tagA!!, start[i++].styleValue, null)
-                    FontVariationAxis(tagA, v)
-                }
-                else -> { // comp > 0
-                    val v = filter(tagB!!, null, end[j++].styleValue)
-                    FontVariationAxis(tagB, v)
-                }
-            }
 
             result.add(axis)
         }
@@ -202,21 +208,21 @@
     // For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
     // Cache hit ratio in the Skia glyph cache.
     private fun adjustWeight(value: Float) =
-            coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
+        coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
 
     // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
     // Cache hit ratio in the Skia glyph cache.
     private fun adjustItalic(value: Float) =
-            coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
+        coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
 
     private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) =
-            (v.coerceIn(min, max) / step).toInt() * step
+        (v.coerceIn(min, max) / step).toInt() * step
 
     companion object {
         private val EMPTY_AXES = arrayOf<FontVariationAxis>()
 
         // Returns true if given two font instance can be interpolated.
         fun canInterpolate(start: Font, end: Font) =
-                start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier
+            start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier
     }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index dfac02d..23e3a01 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -26,6 +26,7 @@
 import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.InsetDrawable
 import android.graphics.drawable.LayerDrawable
+import android.graphics.drawable.StateListDrawable
 import android.util.Log
 import android.view.GhostView
 import android.view.View
@@ -33,8 +34,10 @@
 import android.view.ViewGroupOverlay
 import android.widget.FrameLayout
 import com.android.internal.jank.InteractionJankMonitor
+import java.lang.IllegalArgumentException
 import java.util.LinkedList
 import kotlin.math.min
+import kotlin.math.roundToInt
 
 private const val TAG = "GhostedViewLaunchAnimatorController"
 
@@ -44,12 +47,15 @@
  * of the ghosted view.
  *
  * Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during
- * the animation.
+ * the animation. It must also implement [LaunchableView], otherwise an exception will be thrown
+ * during this controller instantiation.
  *
  * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
  * whenever possible instead.
  */
-open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
+open class GhostedViewLaunchAnimatorController
+@JvmOverloads
+constructor(
     /** The view that will be ghosted and from which the background will be extracted. */
     private val ghostedView: View,
 
@@ -97,6 +103,15 @@
     private val background: Drawable?
 
     init {
+        // Make sure the View we launch from implements LaunchableView to avoid visibility issues.
+        if (ghostedView !is LaunchableView) {
+            throw IllegalArgumentException(
+                "A GhostedViewLaunchAnimatorController was created from a View that does not " +
+                    "implement LaunchableView. This can lead to subtle bugs where the visibility " +
+                    "of the View we are launching from is not what we expected."
+            )
+        }
+
         /** Find the first view with a background in [view] and its children. */
         fun findBackground(view: View): Drawable? {
             if (view.background != null) {
@@ -145,7 +160,8 @@
         val gradient = findGradientDrawable(drawable) ?: return 0f
 
         // TODO(b/184121838): Support more than symmetric top & bottom radius.
-        return gradient.cornerRadii?.get(CORNER_RADIUS_TOP_INDEX) ?: gradient.cornerRadius
+        val radius = gradient.cornerRadii?.get(CORNER_RADIUS_TOP_INDEX) ?: gradient.cornerRadius
+        return radius * ghostedView.scaleX
     }
 
     /** Return the current bottom corner radius of the background. */
@@ -154,7 +170,8 @@
         val gradient = findGradientDrawable(drawable) ?: return 0f
 
         // TODO(b/184121838): Support more than symmetric top & bottom radius.
-        return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
+        val radius = gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
+        return radius * ghostedView.scaleX
     }
 
     override fun createAnimatorState(): LaunchAnimator.State {
@@ -173,9 +190,13 @@
         ghostedView.getLocationOnScreen(ghostedViewLocation)
         val insets = backgroundInsets
         state.top = ghostedViewLocation[1] + insets.top
-        state.bottom = ghostedViewLocation[1] + ghostedView.height - insets.bottom
+        state.bottom =
+            ghostedViewLocation[1] + (ghostedView.height * ghostedView.scaleY).roundToInt() -
+                insets.bottom
         state.left = ghostedViewLocation[0] + insets.left
-        state.right = ghostedViewLocation[0] + ghostedView.width - insets.right
+        state.right =
+            ghostedViewLocation[0] + (ghostedView.width * ghostedView.scaleX).roundToInt() -
+                insets.right
     }
 
     override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -341,6 +362,10 @@
                 }
             }
 
+            if (drawable is StateListDrawable) {
+                return findGradientDrawable(drawable.current)
+            }
+
             return null
         }
     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
new file mode 100644
index 0000000..2eb503b
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableFrameLayout.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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.systemui.animation
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+/** A [FrameLayout] that also implements [LaunchableView]. */
+open class LaunchableFrameLayout : FrameLayout, LaunchableView {
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int
+    ) : super(context, attrs, defStyleAttr)
+
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+        defStyleRes: Int
+    ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        delegate.setVisibility(visibility)
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 5f1bb83..fdab749 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -36,8 +36,8 @@
  * Currently this class can provide text style animation for text weight and text size. For example
  * the simple view that draws text with animating text size is like as follows:
  *
- * <pre>
- * <code>
+ * <pre> <code>
+ * ```
  *     class SimpleTextAnimation : View {
  *         @JvmOverloads constructor(...)
  *
@@ -53,83 +53,63 @@
  *             animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
  *         }
  *     }
- * </code>
- * </pre>
+ * ```
+ * </code> </pre>
  */
-class TextAnimator(
-    layout: Layout,
-    private val invalidateCallback: () -> Unit
-) {
+class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
     // Following two members are for mutable for testing purposes.
     public var textInterpolator: TextInterpolator = TextInterpolator(layout)
-    public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
-        duration = DEFAULT_ANIMATION_DURATION
-        addUpdateListener {
-            textInterpolator.progress = it.animatedValue as Float
-            invalidateCallback()
-        }
-        addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
-                textInterpolator.rebase()
+    public var animator: ValueAnimator =
+        ValueAnimator.ofFloat(1f).apply {
+            duration = DEFAULT_ANIMATION_DURATION
+            addUpdateListener {
+                textInterpolator.progress = it.animatedValue as Float
+                invalidateCallback()
             }
-            override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
-        })
-    }
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator?) {
+                        textInterpolator.rebase()
+                    }
+                    override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
+                }
+            )
+        }
 
     sealed class PositionedGlyph {
 
-        /**
-         * Mutable X coordinate of the glyph position relative from drawing offset.
-         */
+        /** Mutable X coordinate of the glyph position relative from drawing offset. */
         var x: Float = 0f
 
-        /**
-         * Mutable Y coordinate of the glyph position relative from the baseline.
-         */
+        /** Mutable Y coordinate of the glyph position relative from the baseline. */
         var y: Float = 0f
 
-        /**
-         * The current line of text being drawn, in a multi-line TextView.
-         */
+        /** The current line of text being drawn, in a multi-line TextView. */
         var lineNo: Int = 0
 
-        /**
-         * Mutable text size of the glyph in pixels.
-         */
+        /** Mutable text size of the glyph in pixels. */
         var textSize: Float = 0f
 
-        /**
-         * Mutable color of the glyph.
-         */
+        /** Mutable color of the glyph. */
         var color: Int = 0
 
-        /**
-         * Immutable character offset in the text that the current font run start.
-         */
+        /** Immutable character offset in the text that the current font run start. */
         abstract var runStart: Int
             protected set
 
-        /**
-         * Immutable run length of the font run.
-         */
+        /** Immutable run length of the font run. */
         abstract var runLength: Int
             protected set
 
-        /**
-         * Immutable glyph index of the font run.
-         */
+        /** Immutable glyph index of the font run. */
         abstract var glyphIndex: Int
             protected set
 
-        /**
-         * Immutable font instance for this font run.
-         */
+        /** Immutable font instance for this font run. */
         abstract var font: Font
             protected set
 
-        /**
-         * Immutable glyph ID for this glyph.
-         */
+        /** Immutable glyph ID for this glyph. */
         abstract var glyphId: Int
             protected set
     }
@@ -147,20 +127,18 @@
     /**
      * GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
      *
-     * This callback is called for each glyphs just before drawing the glyphs. This function will
-     * be called with the intrinsic position, size, color, glyph ID and font instance. You can
-     * mutate the position, size and color for tweaking animations.
-     * Do not keep the reference of passed glyph object. The interpolator reuses that object for
-     * avoiding object allocations.
+     * This callback is called for each glyphs just before drawing the glyphs. This function will be
+     * called with the intrinsic position, size, color, glyph ID and font instance. You can mutate
+     * the position, size and color for tweaking animations. Do not keep the reference of passed
+     * glyph object. The interpolator reuses that object for avoiding object allocations.
      *
-     * Details:
-     * The text is drawn with font run units. The font run is a text segment that draws with the
-     * same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text
-     * that current glyph is in. Once the font run is determined, the system will convert characters
-     * into glyph IDs. The {@code glyphId} is the glyph identifier in the font and
-     * {@code glyphIndex} is the offset of the converted glyph array. Please note that the
-     * {@code glyphIndex} is not a character index, because the character will not be converted to
-     * glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
+     * Details: The text is drawn with font run units. The font run is a text segment that draws
+     * with the same font. The {@code runStart} and {@code runLimit} is a range of the font run in
+     * the text that current glyph is in. Once the font run is determined, the system will convert
+     * characters into glyph IDs. The {@code glyphId} is the glyph identifier in the font and {@code
+     * glyphIndex} is the offset of the converted glyph array. Please note that the {@code
+     * glyphIndex} is not a character index, because the character will not be converted to glyph
+     * one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
      * composed from multiple characters.
      *
      * Here is an example of font runs: "fin. 終わり"
@@ -193,7 +171,9 @@
      */
     var glyphFilter: GlyphCallback?
         get() = textInterpolator.glyphFilter
-        set(value) { textInterpolator.glyphFilter = value }
+        set(value) {
+            textInterpolator.glyphFilter = value
+        }
 
     fun draw(c: Canvas) = textInterpolator.draw(c)
 
@@ -208,7 +188,7 @@
      * @param weight an optional text weight.
      * @param textSize an optional font size.
      * @param colors an optional colors array that must be the same size as numLines passed to
-     *  the TextInterpolator
+     *               the TextInterpolator
      * @param animate an optional boolean indicating true for showing style transition as animation,
      *                false for immediate style transition. True by default.
      * @param duration an optional animation duration in milliseconds. This is ignored if animate is
@@ -237,10 +217,11 @@
         if (weight >= 0) {
             // Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the
             // memory impact, cache the typeface result.
-            textInterpolator.targetPaint.typeface = typefaceCache.getOrElse(weight) {
-                textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
-                textInterpolator.targetPaint.typeface
-            }
+            textInterpolator.targetPaint.typeface =
+                typefaceCache.getOrElse(weight) {
+                    textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+                    textInterpolator.targetPaint.typeface
+                }
         }
         if (color != null) {
             textInterpolator.targetPaint.color = color
@@ -249,22 +230,24 @@
 
         if (animate) {
             animator.startDelay = delay
-            animator.duration = if (duration == -1L) {
-                DEFAULT_ANIMATION_DURATION
-            } else {
-                duration
-            }
+            animator.duration =
+                if (duration == -1L) {
+                    DEFAULT_ANIMATION_DURATION
+                } else {
+                    duration
+                }
             interpolator?.let { animator.interpolator = it }
             if (onAnimationEnd != null) {
-                val listener = object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
-                        onAnimationEnd.run()
-                        animator.removeListener(this)
+                val listener =
+                    object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator?) {
+                            onAnimationEnd.run()
+                            animator.removeListener(this)
+                        }
+                        override fun onAnimationCancel(animation: Animator?) {
+                            animator.removeListener(this)
+                        }
                     }
-                    override fun onAnimationCancel(animation: Animator?) {
-                        animator.removeListener(this)
-                    }
-                }
                 animator.addListener(listener)
             }
             animator.start()
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 0448c81..341784e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -26,12 +26,8 @@
 import com.android.internal.graphics.ColorUtils
 import java.lang.Math.max
 
-/**
- * Provide text style linear interpolation for plain text.
- */
-class TextInterpolator(
-    layout: Layout
-) {
+/** Provide text style linear interpolation for plain text. */
+class TextInterpolator(layout: Layout) {
 
     /**
      * Returns base paint used for interpolation.
@@ -64,12 +60,11 @@
         var baseFont: Font,
         var targetFont: Font
     ) {
-        val length: Int get() = end - start
+        val length: Int
+            get() = end - start
     }
 
-    /**
-     * A class represents text layout of a single run.
-     */
+    /** A class represents text layout of a single run. */
     private class Run(
         val glyphIds: IntArray,
         val baseX: FloatArray, // same length as glyphIds
@@ -79,12 +74,8 @@
         val fontRuns: List<FontRun>
     )
 
-    /**
-     * A class represents text layout of a single line.
-     */
-    private class Line(
-        val runs: List<Run>
-    )
+    /** A class represents text layout of a single line. */
+    private class Line(val runs: List<Run>)
 
     private var lines = listOf<Line>()
     private val fontInterpolator = FontInterpolator()
@@ -106,8 +97,8 @@
     /**
      * The layout used for drawing text.
      *
-     * Only non-styled text is supported. Even if the given layout is created from Spanned, the
-     * span information is not used.
+     * Only non-styled text is supported. Even if the given layout is created from Spanned, the span
+     * information is not used.
      *
      * The paint objects used for interpolation are not changed by this method call.
      *
@@ -122,6 +113,9 @@
             shapeText(value)
         }
 
+    var shapedText: String = ""
+        private set
+
     init {
         // shapeText needs to be called after all members are initialized.
         shapeText(layout)
@@ -130,8 +124,8 @@
     /**
      * Recalculate internal text layout for interpolation.
      *
-     * Whenever the target paint is modified, call this method to recalculate internal
-     * text layout used for interpolation.
+     * Whenever the target paint is modified, call this method to recalculate internal text layout
+     * used for interpolation.
      */
     fun onTargetPaintModified() {
         updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
@@ -140,8 +134,8 @@
     /**
      * Recalculate internal text layout for interpolation.
      *
-     * Whenever the base paint is modified, call this method to recalculate internal
-     * text layout used for interpolation.
+     * Whenever the base paint is modified, call this method to recalculate internal text layout
+     * used for interpolation.
      */
     fun onBasePaintModified() {
         updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
@@ -152,11 +146,11 @@
      *
      * The text interpolator does not calculate all the text position by text shaper due to
      * performance reasons. Instead, the text interpolator shape the start and end state and
-     * calculate text position of the middle state by linear interpolation. Due to this trick,
-     * the text positions of the middle state is likely different from the text shaper result.
-     * So, if you want to start animation from the middle state, you will see the glyph jumps due to
-     * this trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different
-     * from text shape result of weight 550.
+     * calculate text position of the middle state by linear interpolation. Due to this trick, the
+     * text positions of the middle state is likely different from the text shaper result. So, if
+     * you want to start animation from the middle state, you will see the glyph jumps due to this
+     * trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different from
+     * text shape result of weight 550.
      *
      * After calling this method, do not call onBasePaintModified() since it reshape the text and
      * update the base state. As in above notice, the text shaping result at current progress is
@@ -168,8 +162,8 @@
      * animate weight from 200 to 400, then if you want to move back to 200 at the half of the
      * animation, it will look like
      *
-     * <pre>
-     * <code>
+     * <pre> <code>
+     * ```
      *     val interp = TextInterpolator(layout)
      *
      *     // Interpolate between weight 200 to 400.
@@ -199,9 +193,8 @@
      *         // progress is 0.5
      *         animator.start()
      *     }
-     * </code>
-     * </pre>
-     *
+     * ```
+     * </code> </pre>
      */
     fun rebase() {
         if (progress == 0f) {
@@ -263,69 +256,73 @@
         }
 
         var maxRunLength = 0
-        lines = baseLayout.zip(targetLayout) { baseLine, targetLine ->
-            val runs = baseLine.zip(targetLine) { base, target ->
-
-                require(base.glyphCount() == target.glyphCount()) {
-                    "Inconsistent glyph count at line ${lines.size}"
-                }
-
-                val glyphCount = base.glyphCount()
-
-                // Good to recycle the array if the existing array can hold the new layout result.
-                val glyphIds = IntArray(glyphCount) {
-                    base.getGlyphId(it).also { baseGlyphId ->
-                        require(baseGlyphId == target.getGlyphId(it)) {
-                            "Inconsistent glyph ID at $it in line ${lines.size}"
+        lines =
+            baseLayout.zip(targetLayout) { baseLine, targetLine ->
+                val runs =
+                    baseLine.zip(targetLine) { base, target ->
+                        require(base.glyphCount() == target.glyphCount()) {
+                            "Inconsistent glyph count at line ${lines.size}"
                         }
-                    }
-                }
 
-                val baseX = FloatArray(glyphCount) { base.getGlyphX(it) }
-                val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
-                val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
-                val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
+                        val glyphCount = base.glyphCount()
 
-                // Calculate font runs
-                val fontRun = mutableListOf<FontRun>()
-                if (glyphCount != 0) {
-                    var start = 0
-                    var baseFont = base.getFont(start)
-                    var targetFont = target.getFont(start)
-                    require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
-                        "Cannot interpolate font at $start ($baseFont vs $targetFont)"
-                    }
-
-                    for (i in 1 until glyphCount) {
-                        val nextBaseFont = base.getFont(i)
-                        val nextTargetFont = target.getFont(i)
-
-                        if (baseFont !== nextBaseFont) {
-                            require(targetFont !== nextTargetFont) {
-                                "Base font has changed at $i but target font has not changed."
+                        // Good to recycle the array if the existing array can hold the new layout
+                        // result.
+                        val glyphIds =
+                            IntArray(glyphCount) {
+                                base.getGlyphId(it).also { baseGlyphId ->
+                                    require(baseGlyphId == target.getGlyphId(it)) {
+                                        "Inconsistent glyph ID at $it in line ${lines.size}"
+                                    }
+                                }
                             }
-                            // Font transition point. push run and reset context.
-                            fontRun.add(FontRun(start, i, baseFont, targetFont))
-                            maxRunLength = max(maxRunLength, i - start)
-                            baseFont = nextBaseFont
-                            targetFont = nextTargetFont
-                            start = i
+
+                        val baseX = FloatArray(glyphCount) { base.getGlyphX(it) }
+                        val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
+                        val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
+                        val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
+
+                        // Calculate font runs
+                        val fontRun = mutableListOf<FontRun>()
+                        if (glyphCount != 0) {
+                            var start = 0
+                            var baseFont = base.getFont(start)
+                            var targetFont = target.getFont(start)
                             require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
                                 "Cannot interpolate font at $start ($baseFont vs $targetFont)"
                             }
-                        } else { // baseFont === nextBaseFont
-                            require(targetFont === nextTargetFont) {
-                                "Base font has not changed at $i but target font has changed."
+
+                            for (i in 1 until glyphCount) {
+                                val nextBaseFont = base.getFont(i)
+                                val nextTargetFont = target.getFont(i)
+
+                                if (baseFont !== nextBaseFont) {
+                                    require(targetFont !== nextTargetFont) {
+                                        "Base font has changed at $i but target font is unchanged."
+                                    }
+                                    // Font transition point. push run and reset context.
+                                    fontRun.add(FontRun(start, i, baseFont, targetFont))
+                                    maxRunLength = max(maxRunLength, i - start)
+                                    baseFont = nextBaseFont
+                                    targetFont = nextTargetFont
+                                    start = i
+                                    require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
+                                        "Cannot interpolate font at $start" +
+                                            " ($baseFont vs $targetFont)"
+                                    }
+                                } else { // baseFont === nextBaseFont
+                                    require(targetFont === nextTargetFont) {
+                                        "Base font is unchanged at $i but target font has changed."
+                                    }
+                                }
                             }
+                            fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
+                            maxRunLength = max(maxRunLength, glyphCount - start)
                         }
+                        Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
                     }
-                    fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
-                    maxRunLength = max(maxRunLength, glyphCount - start)
-                }
-                Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
+                Line(runs)
             }
-            Line(runs)
-        }
 
         // Update float array used for drawing.
         if (tmpPositionArray.size < maxRunLength * 2) {
@@ -357,9 +354,9 @@
         if (glyphFilter == null) {
             for (i in run.start until run.end) {
                 tmpPositionArray[arrayIndex++] =
-                        MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
+                    MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
                 tmpPositionArray[arrayIndex++] =
-                        MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+                    MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
             }
             c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint)
             return
@@ -388,13 +385,14 @@
                 tmpPaintForGlyph.color = tmpGlyph.color
 
                 c.drawGlyphs(
-                        line.glyphIds,
-                        prevStart,
-                        tmpPositionArray,
-                        0,
-                        i - prevStart,
-                        font,
-                        tmpPaintForGlyph)
+                    line.glyphIds,
+                    prevStart,
+                    tmpPositionArray,
+                    0,
+                    i - prevStart,
+                    font,
+                    tmpPaintForGlyph
+                )
                 prevStart = i
                 arrayIndex = 0
             }
@@ -404,13 +402,14 @@
         }
 
         c.drawGlyphs(
-                line.glyphIds,
-                prevStart,
-                tmpPositionArray,
-                0,
-                run.end - prevStart,
-                font,
-                tmpPaintForGlyph)
+            line.glyphIds,
+            prevStart,
+            tmpPositionArray,
+            0,
+            run.end - prevStart,
+            font,
+            tmpPaintForGlyph
+        )
     }
 
     private fun updatePositionsAndFonts(
@@ -418,9 +417,7 @@
         updateBase: Boolean
     ) {
         // Update target positions with newly calculated text layout.
-        check(layoutResult.size == lines.size) {
-            "The new layout result has different line count."
-        }
+        check(layoutResult.size == lines.size) { "The new layout result has different line count." }
 
         lines.zip(layoutResult) { line, runs ->
             line.runs.zip(runs) { lineRun, newGlyphs ->
@@ -436,7 +433,7 @@
                         }
                         require(newFont === newGlyphs.getFont(i)) {
                             "The new layout has different font run." +
-                                    " $newFont vs ${newGlyphs.getFont(i)} at $i"
+                                " $newFont vs ${newGlyphs.getFont(i)} at $i"
                         }
                     }
 
@@ -444,7 +441,7 @@
                     // check new font can be interpolatable with base font.
                     require(FontInterpolator.canInterpolate(newFont, run.baseFont)) {
                         "New font cannot be interpolated with existing font. $newFont," +
-                                " ${run.baseFont}"
+                            " ${run.baseFont}"
                     }
 
                     if (updateBase) {
@@ -480,14 +477,13 @@
     }
 
     // Shape the text and stores the result to out argument.
-    private fun shapeText(
-        layout: Layout,
-        paint: TextPaint
-    ): List<List<PositionedGlyphs>> {
+    private fun shapeText(layout: Layout, paint: TextPaint): List<List<PositionedGlyphs>> {
+        var text = StringBuilder()
         val out = mutableListOf<List<PositionedGlyphs>>()
         for (lineNo in 0 until layout.lineCount) { // Shape all lines.
             val lineStart = layout.getLineStart(lineNo)
-            var count = layout.getLineEnd(lineNo) - lineStart
+            val lineEnd = layout.getLineEnd(lineNo)
+            var count = lineEnd - lineStart
             // Do not render the last character in the line if it's a newline and unprintable
             val last = lineStart + count - 1
             if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') {
@@ -495,19 +491,28 @@
             }
 
             val runs = mutableListOf<PositionedGlyphs>()
-            TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
-                    paint) { _, _, glyphs, _ ->
-                runs.add(glyphs)
-            }
+            TextShaper.shapeText(
+                layout.text,
+                lineStart,
+                count,
+                layout.textDirectionHeuristic,
+                paint
+            ) { _, _, glyphs, _ -> runs.add(glyphs) }
             out.add(runs)
+
+            if (lineNo > 0) {
+                text.append("\n")
+            }
+            text.append(layout.text.substring(lineStart, lineEnd))
         }
+        shapedText = text.toString()
         return out
     }
 }
 
 private fun Layout.getDrawOrigin(lineNo: Int) =
-        if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
-            getLineLeft(lineNo)
-        } else {
-            getLineRight(lineNo)
-        }
+    if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
+        getLineLeft(lineNo)
+    } else {
+        getLineRight(lineNo)
+    }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
new file mode 100644
index 0000000..f3d8b17
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpec.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.systemui.animation.back
+
+import android.util.DisplayMetrics
+import android.view.animation.Interpolator
+import android.window.BackEvent
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.util.dpToPx
+
+/** Used to convert [BackEvent] into a [BackTransformation]. */
+fun interface BackAnimationSpec {
+
+    /** Computes transformation based on a [backEvent] and sets it to [result]. */
+    fun getBackTransformation(
+        backEvent: BackEvent,
+        progressY: Float, // TODO(b/265060720): Remove progressY. Could be retrieved from backEvent
+        result: BackTransformation,
+    )
+
+    companion object
+}
+
+/** Create a [BackAnimationSpec] from [displayMetrics] and design specs. */
+fun BackAnimationSpec.Companion.createFloatingSurfaceAnimationSpec(
+    displayMetrics: DisplayMetrics,
+    maxMarginXdp: Float,
+    maxMarginYdp: Float,
+    minScale: Float,
+    translateXEasing: Interpolator = Interpolators.STANDARD_DECELERATE,
+    translateYEasing: Interpolator = Interpolators.LINEAR,
+    scaleEasing: Interpolator = Interpolators.STANDARD_DECELERATE,
+): BackAnimationSpec {
+    val screenWidthPx = displayMetrics.widthPixels
+    val screenHeightPx = displayMetrics.heightPixels
+
+    val maxMarginXPx = maxMarginXdp.dpToPx(displayMetrics)
+    val maxMarginYPx = maxMarginYdp.dpToPx(displayMetrics)
+    val maxTranslationXByScale = (screenWidthPx - screenWidthPx * minScale) / 2
+    val maxTranslationX = maxTranslationXByScale - maxMarginXPx
+    val maxTranslationYByScale = (screenHeightPx - screenHeightPx * minScale) / 2
+    val maxTranslationY = maxTranslationYByScale - maxMarginYPx
+    val minScaleReversed = 1f - minScale
+
+    return BackAnimationSpec { backEvent, progressY, result ->
+        val direction = if (backEvent.swipeEdge == BackEvent.EDGE_LEFT) 1 else -1
+        val progressX = backEvent.progress
+
+        val ratioTranslateX = translateXEasing.getInterpolation(progressX)
+        val ratioTranslateY = translateYEasing.getInterpolation(progressY)
+        val ratioScale = scaleEasing.getInterpolation(progressX)
+
+        result.apply {
+            translateX = ratioTranslateX * direction * maxTranslationX
+            translateY = ratioTranslateY * maxTranslationY
+            scale = 1f - (ratioScale * minScaleReversed)
+        }
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
new file mode 100644
index 0000000..c6b7073
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackAnimationSpecForSysUi.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.systemui.animation.back
+
+import android.util.DisplayMetrics
+
+/**
+ * SysUI transitions - Dismiss app (ST1) Return to launching surface or place of origin
+ * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-1-dismiss-app
+ */
+fun BackAnimationSpec.Companion.dismissAppForSysUi(
+    displayMetrics: DisplayMetrics,
+): BackAnimationSpec =
+    BackAnimationSpec.createFloatingSurfaceAnimationSpec(
+        displayMetrics = displayMetrics,
+        maxMarginXdp = 8f,
+        maxMarginYdp = 8f,
+        minScale = 0.8f,
+    )
+
+/**
+ * SysUI transitions - Cross task (ST2) Return to previous task/app, keeping the current one open
+ * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-2-cross-task
+ */
+fun BackAnimationSpec.Companion.crossTaskForSysUi(
+    displayMetrics: DisplayMetrics,
+): BackAnimationSpec =
+    BackAnimationSpec.createFloatingSurfaceAnimationSpec(
+        displayMetrics = displayMetrics,
+        maxMarginXdp = 8f,
+        maxMarginYdp = 8f,
+        minScale = 0.8f,
+    )
+
+/**
+ * SysUI transitions - Inner area dismiss (ST3) Dismiss non-detachable surface
+ * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-3-inner-area-dismiss
+ */
+fun BackAnimationSpec.Companion.innerAreaDismissForSysUi(
+    displayMetrics: DisplayMetrics,
+): BackAnimationSpec =
+    BackAnimationSpec.createFloatingSurfaceAnimationSpec(
+        displayMetrics = displayMetrics,
+        maxMarginXdp = 0f,
+        maxMarginYdp = 0f,
+        minScale = 0.9f,
+    )
+
+/**
+ * SysUI transitions - Floating system surfaces (ST4)
+ * https://carbon.googleplex.com/predictive-back-for-apps/pages/st-4-floating-system-surfaces
+ */
+fun BackAnimationSpec.Companion.floatingSystemSurfacesForSysUi(
+    displayMetrics: DisplayMetrics,
+): BackAnimationSpec =
+    BackAnimationSpec.createFloatingSurfaceAnimationSpec(
+        displayMetrics = displayMetrics,
+        maxMarginXdp = 8f,
+        maxMarginYdp = 8f,
+        minScale = 0.8f,
+    )
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
new file mode 100644
index 0000000..49d1fb4
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/BackTransformation.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.systemui.animation.back
+
+import android.view.View
+
+/**
+ * This object that represents the transformation to apply to the target. The properties of this
+ * object are mutable for performance reasons (avoid recreating this object)
+ */
+data class BackTransformation(
+    var translateX: Float = Float.NaN,
+    var translateY: Float = Float.NaN,
+    var scale: Float = Float.NaN,
+)
+
+/** Apply the transformation to the [targetView] */
+fun BackTransformation.applyTo(targetView: View) {
+    if (translateX.isFinite()) targetView.translationX = translateX
+    if (translateY.isFinite()) targetView.translationY = translateY
+    if (scale.isFinite()) {
+        targetView.scaleX = scale
+        targetView.scaleY = scale
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
new file mode 100644
index 0000000..33d14b1
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.systemui.animation.back
+
+import android.util.DisplayMetrics
+import android.window.BackEvent
+import android.window.OnBackAnimationCallback
+
+/**
+ * Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be
+ * called on each update passing the current [BackTransformation].
+ *
+ * Optionally, you can specify [onBackStarted], [onBackInvoked], and [onBackCancelled] callbacks.
+ */
+fun onBackAnimationCallbackFrom(
+    backAnimationSpec: BackAnimationSpec,
+    displayMetrics: DisplayMetrics, // TODO(b/265060720): We could remove this
+    onBackProgressed: (BackTransformation) -> Unit,
+    onBackStarted: (BackEvent) -> Unit = {},
+    onBackInvoked: () -> Unit = {},
+    onBackCancelled: () -> Unit = {},
+): OnBackAnimationCallback {
+    return object : OnBackAnimationCallback {
+        private var initialY = 0f
+        private val lastTransformation = BackTransformation()
+
+        override fun onBackStarted(backEvent: BackEvent) {
+            initialY = backEvent.touchY
+            onBackStarted(backEvent)
+        }
+
+        override fun onBackProgressed(backEvent: BackEvent) {
+            val progressY = (backEvent.touchY - initialY) / displayMetrics.heightPixels
+
+            backAnimationSpec.getBackTransformation(
+                backEvent = backEvent,
+                progressY = progressY,
+                result = lastTransformation,
+            )
+
+            onBackProgressed(lastTransformation)
+        }
+
+        override fun onBackInvoked() {
+            onBackInvoked()
+        }
+
+        override fun onBackCancelled() {
+            onBackCancelled()
+        }
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index 0e3d41c..7897934 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -48,7 +48,7 @@
         animator.addUpdateListener { updateListener ->
             val now = updateListener.currentPlayTime
             val progress = updateListener.animatedValue as Float
-            rippleShader.progress = progress
+            rippleShader.rawProgress = progress
             rippleShader.distortionStrength = if (config.shouldDistort) 1 - progress else 0f
             rippleShader.time = now.toFloat()
         }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index 9058510..74bc910 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -18,6 +18,7 @@
 import android.graphics.PointF
 import android.graphics.RuntimeShader
 import android.util.MathUtils
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
 import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
 
@@ -43,11 +44,24 @@
     }
     // language=AGSL
     companion object {
+        // Default fade in/ out values. The value range is [0,1].
+        const val DEFAULT_FADE_IN_START = 0f
+        const val DEFAULT_FADE_OUT_END = 1f
+
+        const val DEFAULT_BASE_RING_FADE_IN_END = 0.1f
+        const val DEFAULT_BASE_RING_FADE_OUT_START = 0.3f
+
+        const val DEFAULT_SPARKLE_RING_FADE_IN_END = 0.1f
+        const val DEFAULT_SPARKLE_RING_FADE_OUT_START = 0.4f
+
+        const val DEFAULT_CENTER_FILL_FADE_IN_END = 0f
+        const val DEFAULT_CENTER_FILL_FADE_OUT_START = 0f
+        const val DEFAULT_CENTER_FILL_FADE_OUT_END = 0.6f
+
         private const val SHADER_UNIFORMS =
             """
             uniform vec2 in_center;
             uniform vec2 in_size;
-            uniform float in_progress;
             uniform float in_cornerRadius;
             uniform float in_thickness;
             uniform float in_time;
@@ -143,11 +157,26 @@
             }
 
         private fun subProgress(start: Float, end: Float, progress: Float): Float {
+            // Avoid division by 0.
+            if (start == end) {
+                // If start and end are the same and progress has exceeded the start/ end point,
+                // treat it as 1, otherwise 0.
+                return if (progress > start) 1f else 0f
+            }
+
             val min = Math.min(start, end)
             val max = Math.max(start, end)
             val sub = Math.min(Math.max(progress, min), max)
             return (sub - start) / (end - start)
         }
+
+        private fun getFade(fadeParams: FadeParams, rawProgress: Float): Float {
+            val fadeIn = subProgress(fadeParams.fadeInStart, fadeParams.fadeInEnd, rawProgress)
+            val fadeOut =
+                1f - subProgress(fadeParams.fadeOutStart, fadeParams.fadeOutEnd, rawProgress)
+
+            return Math.min(fadeIn, fadeOut)
+        }
     }
 
     /** Sets the center position of the ripple. */
@@ -162,33 +191,33 @@
         maxSize.y = height
     }
 
-    /** Progress of the ripple. Float value between [0, 1]. */
-    var progress: Float = 0.0f
+    /**
+     * Linear progress of the ripple. Float value between [0, 1].
+     *
+     * <p>Note that the progress here is expected to be linear without any curve applied.
+     */
+    var rawProgress: Float = 0.0f
         set(value) {
             field = value
-            setFloatUniform("in_progress", value)
-            val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
+            progress = Interpolators.STANDARD.getInterpolation(value)
 
-            currentWidth = maxSize.x * curvedProg
-            currentHeight = maxSize.y * curvedProg
-            setFloatUniform("in_size", /* width= */ currentWidth, /* height= */ currentHeight)
-            setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
+            setFloatUniform("in_fadeSparkle", getFade(sparkleRingFadeParams, value))
+            setFloatUniform("in_fadeRing", getFade(baseRingFadeParams, value))
+            setFloatUniform("in_fadeFill", getFade(centerFillFadeParams, value))
+        }
+
+    /** Progress with Standard easing curve applied. */
+    private var progress: Float = 0.0f
+        set(value) {
+            currentWidth = maxSize.x * value
+            currentHeight = maxSize.y * value
+            setFloatUniform("in_size", currentWidth, currentHeight)
+
+            setFloatUniform("in_thickness", maxSize.y * value * 0.5f)
             // radius should not exceed width and height values.
-            setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
+            setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value)
 
             setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
-
-            val fadeIn = subProgress(0f, 0.1f, value)
-            val fadeOutNoise = subProgress(0.4f, 1f, value)
-            var fadeOutRipple = 0f
-            var fadeFill = 0f
-            if (!rippleFill) {
-                fadeFill = subProgress(0f, 0.6f, value)
-                fadeOutRipple = subProgress(0.3f, 1f, value)
-            }
-            setFloatUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
-            setFloatUniform("in_fadeFill", 1 - fadeFill)
-            setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
         }
 
     /** Play time since the start of the effect. */
@@ -220,25 +249,134 @@
     var distortionStrength: Float = 0.0f
         set(value) {
             field = value
-            setFloatUniform("in_distort_radial", 75 * progress * value)
+            setFloatUniform("in_distort_radial", 75 * rawProgress * value)
             setFloatUniform("in_distort_xy", 75 * value)
         }
 
+    /**
+     * Pixel density of the screen that the effects are rendered to.
+     *
+     * <p>This value should come from [resources.displayMetrics.density].
+     */
     var pixelDensity: Float = 1.0f
         set(value) {
             field = value
             setFloatUniform("in_pixelDensity", value)
         }
 
-    /**
-     * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
-     * False for a ring effect.
-     */
-    var rippleFill: Boolean = false
-
     var currentWidth: Float = 0f
         private set
 
     var currentHeight: Float = 0f
         private set
+
+    /**
+     * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
+     * False for a ring effect.
+     *
+     * <p>You must reset fade params after changing this.
+     *
+     * TODO(b/265326983): Remove this and only expose fade params.
+     */
+    var rippleFill: Boolean = false
+        set(value) {
+            if (value) {
+                baseRingFadeParams.fadeOutStart = 1f
+                baseRingFadeParams.fadeOutEnd = 1f
+
+                centerFillFadeParams.fadeInStart = 0f
+                centerFillFadeParams.fadeInEnd = 0f
+                centerFillFadeParams.fadeOutStart = 1f
+                centerFillFadeParams.fadeOutEnd = 1f
+            } else {
+                // Set back to the original fade parameters.
+                // Ideally this should be set by the client as they know the initial value.
+                baseRingFadeParams.fadeOutStart = DEFAULT_BASE_RING_FADE_OUT_START
+                baseRingFadeParams.fadeOutEnd = DEFAULT_FADE_OUT_END
+
+                centerFillFadeParams.fadeInStart = DEFAULT_FADE_IN_START
+                centerFillFadeParams.fadeInEnd = DEFAULT_CENTER_FILL_FADE_IN_END
+                centerFillFadeParams.fadeOutStart = DEFAULT_CENTER_FILL_FADE_OUT_START
+                centerFillFadeParams.fadeOutEnd = DEFAULT_CENTER_FILL_FADE_OUT_END
+            }
+            field = value
+        }
+
+    /** Parameters that are used to fade in/ out of the sparkle ring. */
+    val sparkleRingFadeParams =
+        FadeParams(
+            DEFAULT_FADE_IN_START,
+            DEFAULT_SPARKLE_RING_FADE_IN_END,
+            DEFAULT_SPARKLE_RING_FADE_OUT_START,
+            DEFAULT_FADE_OUT_END
+        )
+
+    /**
+     * Parameters that are used to fade in/ out of the base ring.
+     *
+     * <p>Note that the shader draws the sparkle ring on top of the base ring.
+     */
+    val baseRingFadeParams =
+        FadeParams(
+            DEFAULT_FADE_IN_START,
+            DEFAULT_BASE_RING_FADE_IN_END,
+            DEFAULT_BASE_RING_FADE_OUT_START,
+            DEFAULT_FADE_OUT_END
+        )
+
+    /**
+     * Parameters that are used to fade in/ out of the center fill.
+     *
+     * <p>Note that if [rippleFill] is set to true, those will be ignored and the center fill will
+     * be always full alpha.
+     */
+    val centerFillFadeParams =
+        FadeParams(
+            DEFAULT_FADE_IN_START,
+            DEFAULT_CENTER_FILL_FADE_IN_END,
+            DEFAULT_CENTER_FILL_FADE_OUT_START,
+            DEFAULT_CENTER_FILL_FADE_OUT_END
+        )
+
+    /**
+     * Parameters used for fade in and outs of the ripple.
+     *
+     * <p>Note that all the fade in/ outs are "linear" progression.
+     * ```
+     *          (opacity)
+     *          1
+     *          │
+     * maxAlpha ←       ――――――――――――
+     *          │      /            \
+     *          │     /              \
+     * minAlpha ←――――/                \―――― (alpha change)
+     *          │
+     *          │
+     *          0 ―――↑―――↑―――――――――↑―――↑――――1 (progress)
+     *               fadeIn        fadeOut
+     *               Start & End   Start & End
+     * ```
+     * <p>If no fade in/ out is needed, set [fadeInStart] and [fadeInEnd] to 0; [fadeOutStart] and
+     * [fadeOutEnd] to 1.
+     */
+    data class FadeParams(
+        /**
+         * The starting point of the fade out which ends at [fadeInEnd], given that the animation
+         * goes from 0 to 1.
+         */
+        var fadeInStart: Float = DEFAULT_FADE_IN_START,
+        /**
+         * The endpoint of the fade in when the fade in starts at [fadeInStart], given that the
+         * animation goes from 0 to 1.
+         */
+        var fadeInEnd: Float,
+        /**
+         * The starting point of the fade out which ends at 1, given that the animation goes from 0
+         * to 1.
+         */
+        var fadeOutStart: Float,
+
+        /** The endpoint of the fade out, given that the animation goes from 0 to 1. */
+        var fadeOutEnd: Float = DEFAULT_FADE_OUT_END,
+    )
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index b37c734..3c9328c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -77,7 +77,7 @@
         rippleShader = RippleShader(rippleShape)
 
         rippleShader.color = RippleAnimationConfig.RIPPLE_DEFAULT_COLOR
-        rippleShader.progress = 0f
+        rippleShader.rawProgress = 0f
         rippleShader.sparkleStrength = RippleAnimationConfig.RIPPLE_SPARKLE_STRENGTH
         rippleShader.pixelDensity = resources.displayMetrics.density
 
@@ -93,7 +93,7 @@
         animator.addUpdateListener { updateListener ->
             val now = updateListener.currentPlayTime
             val progress = updateListener.animatedValue as Float
-            rippleShader.progress = progress
+            rippleShader.rawProgress = progress
             rippleShader.distortionStrength = 1 - progress
             rippleShader.time = now.toFloat()
             invalidate()
@@ -117,15 +117,6 @@
         rippleShader.color = ColorUtils.setAlphaComponent(color, alpha)
     }
 
-    /**
-     * Set whether the ripple should remain filled as the ripple expands.
-     *
-     * See [RippleShader.rippleFill].
-     */
-    fun setRippleFill(rippleFill: Boolean) {
-        rippleShader.rippleFill = rippleFill
-    }
-
     /** Set the intensity of the sparkles. */
     fun setSparkleStrength(strength: Float) {
         rippleShader.sparkleStrength = strength
@@ -142,25 +133,13 @@
         }
         // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the
         // active effect area. Values here should be kept in sync with the animation implementation
-        // in the ripple shader.
+        // in the ripple shader. (Twice bigger)
         if (rippleShape == RippleShape.CIRCLE) {
-            val maskRadius =
-                (1 -
-                    (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress)) * maxWidth
+            val maskRadius = rippleShader.currentWidth
             canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
         } else {
-            val maskWidth =
-                (1 -
-                    (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress)) * maxWidth * 2
-            val maskHeight =
-                (1 -
-                    (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress) *
-                        (1 - rippleShader.progress)) * maxHeight * 2
+            val maskWidth = rippleShader.currentWidth * 2
+            val maskHeight = rippleShader.currentHeight * 2
             canvas.drawRect(
                 /* left= */ centerX - maskWidth,
                 /* top= */ centerY - maskHeight,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt
new file mode 100644
index 0000000..4bc9972
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dimension.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.util.DisplayMetrics
+import android.util.TypedValue
+
+/** Convert [this] number of dps to device pixels. */
+fun Number.dpToPx(context: Context): Float = dpToPx(resources = context.resources)
+
+/** Convert [this] number of dps to device pixels. */
+fun Number.dpToPx(resources: Resources): Float = dpToPx(displayMetrics = resources.displayMetrics)
+
+/** Convert [this] number of dps to device pixels. */
+fun Number.dpToPx(displayMetrics: DisplayMetrics): Float =
+    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, toFloat(), displayMetrics)
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt
new file mode 100644
index 0000000..1f6e603
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UFile
+import org.jetbrains.uast.UImportStatement
+
+/**
+ * Detects violations of the Dependency Rule of Clean Architecture.
+ *
+ * The rule states that code in each layer may only depend on code in the same layer or the layer
+ * directly "beneath" that layer in the layer diagram.
+ *
+ * In System UI, we have three layers; from top to bottom, they are: ui, domain, and data. As a
+ * convention, was used packages with those names to place code in the appropriate layer. We also
+ * make an exception and allow for shared models to live under a separate package named "shared" to
+ * avoid code duplication.
+ *
+ * For more information, please see go/sysui-arch.
+ */
+@Suppress("UnstableApiUsage")
+class CleanArchitectureDependencyViolationDetector : Detector(), Detector.UastScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement>> {
+        return listOf(UFile::class.java)
+    }
+
+    override fun createUastHandler(context: JavaContext): UElementHandler {
+        return object : UElementHandler() {
+            override fun visitFile(node: UFile) {
+                // Check which Clean Architecture layer this file belongs to:
+                matchingLayer(node.packageName)?.let { layer ->
+                    // The file matches with a Clean Architecture layer. Let's check all of its
+                    // imports.
+                    node.imports.forEach { importStatement ->
+                        visitImportStatement(context, layer, importStatement)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun visitImportStatement(
+        context: JavaContext,
+        layer: Layer,
+        importStatement: UImportStatement,
+    ) {
+        val importText = importStatement.importReference?.asSourceString() ?: return
+        val importedLayer = matchingLayer(importText) ?: return
+
+        // Now check whether the layer of the file may depend on the layer of the import.
+        if (!layer.mayDependOn(importedLayer)) {
+            context.report(
+                issue = ISSUE,
+                scope = importStatement,
+                location = context.getLocation(importStatement),
+                message =
+                    "The ${layer.packageNamePart} layer may not depend on" +
+                        " the ${importedLayer.packageNamePart} layer.",
+            )
+        }
+    }
+
+    private fun matchingLayer(packageName: String): Layer? {
+        val packageNameParts = packageName.split(".").toSet()
+        return Layer.values()
+            .filter { layer -> packageNameParts.contains(layer.packageNamePart) }
+            .takeIf { it.size == 1 }
+            ?.first()
+    }
+
+    private enum class Layer(
+        val packageNamePart: String,
+        val canDependOn: Set<Layer>,
+    ) {
+        SHARED(
+            packageNamePart = "shared",
+            canDependOn = emptySet(), // The shared layer may not depend on any other layer.
+        ),
+        DATA(
+            packageNamePart = "data",
+            canDependOn = setOf(SHARED),
+        ),
+        DOMAIN(
+            packageNamePart = "domain",
+            canDependOn = setOf(SHARED, DATA),
+        ),
+        UI(
+            packageNamePart = "ui",
+            canDependOn = setOf(DOMAIN, SHARED),
+        ),
+        ;
+
+        fun mayDependOn(otherLayer: Layer): Boolean {
+            return this == otherLayer || canDependOn.contains(otherLayer)
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        val ISSUE =
+            Issue.create(
+                id = "CleanArchitectureDependencyViolation",
+                briefDescription = "Violation of the Clean Architecture Dependency Rule.",
+                explanation =
+                    """
+                    Following the \"Dependency Rule\" from Clean Architecture, every layer of code \
+                    can only depend code in its own layer or code in the layer directly \
+                    \"beneath\" it. Therefore, the UI layer can only depend on the" Domain layer \
+                    and the Domain layer can only depend on the Data layer. We" do make an \
+                    exception to allow shared models to exist and be shared across layers by \
+                    placing them under shared/model, which should be done with care. For more \
+                    information about Clean Architecture in System UI, please see go/sysui-arch. \
+                    NOTE: if your code is not using Clean Architecture, please feel free to ignore \
+                    this warning.
+                """,
+                category = Category.CORRECTNESS,
+                priority = 8,
+                severity = Severity.WARNING,
+                implementation =
+                    Implementation(
+                        CleanArchitectureDependencyViolationDetector::class.java,
+                        Scope.JAVA_FILE_SCOPE,
+                    ),
+            )
+    }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 3f334c1c..254a6fb 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -27,9 +27,11 @@
 class SystemUIIssueRegistry : IssueRegistry() {
 
     override val issues: List<Issue>
-        get() = listOf(
+        get() =
+            listOf(
                 BindServiceOnMainThreadDetector.ISSUE,
                 BroadcastSentViaContextDetector.ISSUE,
+                CleanArchitectureDependencyViolationDetector.ISSUE,
                 SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
                 SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY,
                 NonInjectedMainThreadDetector.ISSUE,
@@ -37,7 +39,7 @@
                 SoftwareBitmapDetector.ISSUE,
                 NonInjectedServiceDetector.ISSUE,
                 StaticSettingsProviderDetector.ISSUE
-        )
+            )
 
     override val api: Int
         get() = CURRENT_API
@@ -45,9 +47,9 @@
         get() = 8
 
     override val vendor: Vendor =
-            Vendor(
-                    vendorName = "Android",
-                    feedbackUrl = "http://b/issues/new?component=78010",
-                    contact = "jernej@google.com"
-            )
+        Vendor(
+            vendorName = "Android",
+            feedbackUrl = "http://b/issues/new?component=78010",
+            contact = "jernej@google.com"
+        )
 }
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
new file mode 100644
index 0000000..a4b59fd
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
@@ -0,0 +1,296 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Ignore
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+@Ignore("b/254533331")
+class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
+    override fun getDetector(): Detector {
+        return CleanArchitectureDependencyViolationDetector()
+    }
+
+    override fun getIssues(): List<Issue> {
+        return listOf(
+            CleanArchitectureDependencyViolationDetector.ISSUE,
+        )
+    }
+
+    @Test
+    fun `No violations`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .run()
+            .expectWarningCount(0)
+    }
+
+    @Test
+    fun `Violation - domain depends on ui`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+                TestFiles.kotlin(
+                    """
+                        package test.domain.interactor
+
+                        import test.ui.viewmodel.ViewModel
+
+                        class BadClass(
+                            private val viewModel: ViewModel,
+                        )
+                    """.trimIndent()
+                )
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectWarningCount(1)
+            .expect(
+                expectedText =
+                    """
+                    src/test/domain/interactor/BadClass.kt:3: Warning: The domain layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
+                    import test.ui.viewmodel.ViewModel
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                """,
+            )
+    }
+
+    @Test
+    fun `Violation - ui depends on data`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+                TestFiles.kotlin(
+                    """
+                        package test.ui.viewmodel
+
+                        import test.data.repository.Repository
+
+                        class BadClass(
+                            private val repository: Repository,
+                        )
+                    """.trimIndent()
+                )
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectWarningCount(1)
+            .expect(
+                expectedText =
+                    """
+                    src/test/ui/viewmodel/BadClass.kt:3: Warning: The ui layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
+                    import test.data.repository.Repository
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                """,
+            )
+    }
+
+    @Test
+    fun `Violation - shared depends on all other layers`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+                TestFiles.kotlin(
+                    """
+                        package test.shared.model
+
+                        import test.data.repository.Repository
+                        import test.domain.interactor.Interactor
+                        import test.ui.viewmodel.ViewModel
+
+                        class BadClass(
+                            private val repository: Repository,
+                            private val interactor: Interactor,
+                            private val viewmodel: ViewModel,
+                        )
+                    """.trimIndent()
+                )
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectWarningCount(3)
+            .expect(
+                expectedText =
+                    """
+                    src/test/shared/model/BadClass.kt:3: Warning: The shared layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
+                    import test.data.repository.Repository
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/test/shared/model/BadClass.kt:4: Warning: The shared layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
+                    import test.domain.interactor.Interactor
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    src/test/shared/model/BadClass.kt:5: Warning: The shared layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
+                    import test.ui.viewmodel.ViewModel
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 3 warnings
+                """,
+            )
+    }
+
+    @Test
+    fun `Violation - data depends on domain`() {
+        lint()
+            .files(
+                *LEGITIMATE_FILES,
+                TestFiles.kotlin(
+                    """
+                        package test.data.repository
+
+                        import test.domain.interactor.Interactor
+
+                        class BadClass(
+                            private val interactor: Interactor,
+                        )
+                    """.trimIndent()
+                )
+            )
+            .issues(
+                CleanArchitectureDependencyViolationDetector.ISSUE,
+            )
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectWarningCount(1)
+            .expect(
+                expectedText =
+                    """
+                    src/test/data/repository/BadClass.kt:3: Warning: The data layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
+                    import test.domain.interactor.Interactor
+                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                    0 errors, 1 warnings
+                """,
+            )
+    }
+
+    companion object {
+        private val MODEL_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.shared.model
+
+                    import test.some.other.thing.SomeOtherThing
+
+                    data class Model(
+                        private val name: String,
+                    )
+                """.trimIndent()
+            )
+        private val REPOSITORY_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.data.repository
+
+                    import test.shared.model.Model
+                    import test.some.other.thing.SomeOtherThing
+
+                    class Repository {
+                        private val models = listOf(
+                            Model("one"),
+                            Model("two"),
+                            Model("three"),
+                        )
+
+                        fun getModels(): List<Model> {
+                            return models
+                        }
+                    }
+                """.trimIndent()
+            )
+        private val INTERACTOR_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.domain.interactor
+
+                    import test.data.repository.Repository
+                    import test.shared.model.Model
+
+                    class Interactor(
+                        private val repository: Repository,
+                    ) {
+                        fun getModels(): List<Model> {
+                            return repository.getModels()
+                        }
+                    }
+                """.trimIndent()
+            )
+        private val VIEW_MODEL_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.ui.viewmodel
+
+                    import test.domain.interactor.Interactor
+                    import test.some.other.thing.SomeOtherThing
+
+                    class ViewModel(
+                        private val interactor: Interactor,
+                    ) {
+                        fun getNames(): List<String> {
+                            return interactor.getModels().map { model -> model.name }
+                        }
+                    }
+                """.trimIndent()
+            )
+        private val NON_CLEAN_ARCHITECTURE_FILE =
+            TestFiles.kotlin(
+                """
+                    package test.some.other.thing
+
+                    import test.data.repository.Repository
+                    import test.domain.interactor.Interactor
+                    import test.ui.viewmodel.ViewModel
+
+                    class SomeOtherThing {
+                        init {
+                            val viewModel = ViewModel(
+                                interactor = Interactor(
+                                    repository = Repository(),
+                                ),
+                            )
+                        }
+                    }
+                """.trimIndent()
+            )
+        private val LEGITIMATE_FILES =
+            arrayOf(
+                MODEL_FILE,
+                REPOSITORY_FILE,
+                INTERACTOR_FILE,
+                VIEW_MODEL_FILE,
+                NON_CLEAN_ARCHITECTURE_FILE,
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index 4e96dda..cfc38df 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -33,8 +33,8 @@
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.LocalContentColor
-import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
 import androidx.compose.material3.contentColorFor
+import androidx.compose.material3.minimumInteractiveComponentSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
@@ -65,21 +65,17 @@
 import androidx.compose.ui.graphics.drawscope.scale
 import androidx.compose.ui.layout.boundsInRoot
 import androidx.compose.ui.layout.findRootCoordinates
-import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.ViewTreeLifecycleOwner
 import androidx.lifecycle.ViewTreeViewModelStoreOwner
-import com.android.compose.runtime.movableContentOf
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.max
 import kotlin.math.min
-import kotlin.math.roundToInt
 
 /**
  * Create an expandable shape that can launch into an Activity or a Dialog.
@@ -220,21 +216,8 @@
     // If this expandable is expanded when it's being directly clicked on, let's ensure that it has
     // the minimum interactive size followed by all M3 components (48.dp).
     val minInteractiveSizeModifier =
-        if (onClick != null && LocalMinimumTouchTargetEnforcement.current) {
-            // TODO(b/242040009): Replace this by Modifier.minimumInteractiveComponentSize() once
-            // http://aosp/2305511 is available.
-            val minTouchSize = LocalViewConfiguration.current.minimumTouchTargetSize
-            Modifier.layout { measurable, constraints ->
-                // Copied from androidx.compose.material3.InteractiveComponentSize.kt
-                val placeable = measurable.measure(constraints)
-                val width = maxOf(placeable.width, minTouchSize.width.roundToPx())
-                val height = maxOf(placeable.height, minTouchSize.height.roundToPx())
-                layout(width, height) {
-                    val centerX = ((width - placeable.width) / 2f).roundToInt()
-                    val centerY = ((height - placeable.height) / 2f).roundToInt()
-                    placeable.place(centerX, centerY)
-                }
-            }
+        if (onClick != null) {
+            Modifier.minimumInteractiveComponentSize()
         } else {
             Modifier
         }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 59b4848..a523cf1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,15 +21,14 @@
 import android.provider.Settings
 import android.util.Log
 import androidx.annotation.OpenForTesting
-import com.android.internal.annotations.Keep
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.plugins.ClockProvider
 import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.ClockSettings
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
-import org.json.JSONObject
 
 private val TAG = ClockRegistry::class.simpleName
 private const val DEBUG = true
@@ -64,34 +63,54 @@
             disconnectClocks(plugin)
     }
 
-    open var currentClockId: ClockId
+    open var settings: ClockSettings?
         get() {
-            return try {
+            try {
                 val json = Settings.Secure.getString(
                     context.contentResolver,
                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
                 )
                 if (json == null || json.isEmpty()) {
-                    return fallbackClockId
+                    return null
                 }
-                ClockSetting.deserialize(json).clockId
+                return ClockSettings.deserialize(json)
             } catch (ex: Exception) {
-                Log.e(TAG, "Failed to parse clock setting", ex)
-                fallbackClockId
+                Log.e(TAG, "Failed to parse clock settings", ex)
+                return null
             }
         }
-        set(value) {
+        protected set(value) {
             try {
-                val json = ClockSetting.serialize(ClockSetting(value, System.currentTimeMillis()))
+                val json = if (value != null) {
+                    value._applied_timestamp = System.currentTimeMillis()
+                    ClockSettings.serialize(value)
+                } else {
+                    ""
+                }
+
                 Settings.Secure.putString(
                     context.contentResolver,
                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
                 )
             } catch (ex: Exception) {
-                Log.e(TAG, "Failed to set clock setting", ex)
+                Log.e(TAG, "Failed to set clock settings", ex)
             }
         }
 
+    private fun mutateSetting(mutator: (ClockSettings) -> Unit) {
+        val settings = this.settings ?: ClockSettings()
+        mutator(settings)
+        this.settings = settings
+    }
+
+    var currentClockId: ClockId
+        get() = settings?.clockId ?: fallbackClockId
+        set(value) { mutateSetting { it.clockId = value } }
+
+    var seedColor: Int?
+        get() = settings?.seedColor
+        set(value) { mutateSetting { it.seedColor = value } }
+
     init {
         connectClocks(defaultClockProvider)
         if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
@@ -194,36 +213,16 @@
         return createClock(DEFAULT_CLOCK_ID)!!
     }
 
-    private fun createClock(clockId: ClockId): ClockController? =
-        availableClocks[clockId]?.provider?.createClock(clockId)
+    private fun createClock(clockId: ClockId): ClockController? {
+        val settings = this.settings ?: ClockSettings()
+        if (clockId != settings.clockId) {
+            settings.clockId = clockId
+        }
+        return availableClocks[clockId]?.provider?.createClock(settings)
+    }
 
     private data class ClockInfo(
         val metadata: ClockMetadata,
         val provider: ClockProvider
     )
-
-    @Keep
-    data class ClockSetting(
-        val clockId: ClockId,
-        val _applied_timestamp: Long?
-    ) {
-        companion object {
-            private val KEY_CLOCK_ID = "clockId"
-            private val KEY_TIMESTAMP = "_applied_timestamp"
-
-            fun serialize(setting: ClockSetting): String {
-                return JSONObject()
-                    .put(KEY_CLOCK_ID, setting.clockId)
-                    .put(KEY_TIMESTAMP, setting._applied_timestamp)
-                    .toString()
-            }
-
-            fun deserialize(jsonStr: String): ClockSetting {
-                val json = JSONObject(jsonStr)
-                return ClockSetting(
-                    json.getString(KEY_CLOCK_ID),
-                    if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null)
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 7645dec..2a40f5c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.plugins.ClockEvents
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.ClockSettings
 import com.android.systemui.plugins.log.LogBuffer
 import java.io.PrintWriter
 import java.util.Locale
@@ -46,6 +47,7 @@
     ctx: Context,
     private val layoutInflater: LayoutInflater,
     private val resources: Resources,
+    private val settings: ClockSettings?,
 ) : ClockController {
     override val smallClock: DefaultClockFaceController
     override val largeClock: LargeClockFaceController
@@ -66,12 +68,14 @@
         smallClock =
             DefaultClockFaceController(
                 layoutInflater.inflate(R.layout.clock_default_small, parent, false)
-                    as AnimatableClockView
+                    as AnimatableClockView,
+                settings?.seedColor
             )
         largeClock =
             LargeClockFaceController(
                 layoutInflater.inflate(R.layout.clock_default_large, parent, false)
-                    as AnimatableClockView
+                    as AnimatableClockView,
+                settings?.seedColor
             )
         clocks = listOf(smallClock.view, largeClock.view)
 
@@ -85,11 +89,13 @@
         animations = DefaultClockAnimations(dozeFraction, foldFraction)
         events.onColorPaletteChanged(resources)
         events.onTimeZoneChanged(TimeZone.getDefault())
-        events.onTimeTick()
+        smallClock.events.onTimeTick()
+        largeClock.events.onTimeTick()
     }
 
     open inner class DefaultClockFaceController(
         override val view: AnimatableClockView,
+        val seedColor: Int?,
     ) : ClockFaceController {
 
         // MAGENTA is a placeholder, and will be assigned correctly in initialize
@@ -104,11 +110,16 @@
             }
 
         init {
+            if (seedColor != null) {
+                currentColor = seedColor
+            }
             view.setColors(currentColor, currentColor)
         }
 
         override val events =
             object : ClockFaceEvents {
+                override fun onTimeTick() = view.refreshTime()
+
                 override fun onRegionDarknessChanged(isRegionDark: Boolean) {
                     this@DefaultClockFaceController.isRegionDark = isRegionDark
                     updateColor()
@@ -129,7 +140,9 @@
 
         fun updateColor() {
             val color =
-                if (isRegionDark) {
+                if (seedColor != null) {
+                    seedColor
+                } else if (isRegionDark) {
                     resources.getColor(android.R.color.system_accent1_100)
                 } else {
                     resources.getColor(android.R.color.system_accent2_600)
@@ -149,7 +162,8 @@
 
     inner class LargeClockFaceController(
         view: AnimatableClockView,
-    ) : DefaultClockFaceController(view) {
+        seedColor: Int?,
+    ) : DefaultClockFaceController(view, seedColor) {
         override fun recomputePadding(targetRegion: Rect?) {
             // We center the view within the targetRegion instead of within the parent
             // view by computing the difference and adding that to the padding.
@@ -169,8 +183,6 @@
     }
 
     inner class DefaultClockEvents : ClockEvents {
-        override fun onTimeTick() = clocks.forEach { it.refreshTime() }
-
         override fun onTimeFormatChanged(is24Hr: Boolean) =
             clocks.forEach { it.refreshFormat(is24Hr) }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 4c0504b..0fd1b49 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.plugins.ClockProvider
+import com.android.systemui.plugins.ClockSettings
 
 private val TAG = DefaultClockProvider::class.simpleName
 const val DEFAULT_CLOCK_NAME = "Default Clock"
@@ -36,12 +37,12 @@
     override fun getClocks(): List<ClockMetadata> =
         listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
 
-    override fun createClock(id: ClockId): ClockController {
-        if (id != DEFAULT_CLOCK_ID) {
-            throw IllegalArgumentException("$id is unsupported by $TAG")
+    override fun createClock(settings: ClockSettings): ClockController {
+        if (settings.clockId != DEFAULT_CLOCK_ID) {
+            throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
         }
 
-        return DefaultClockController(ctx, layoutInflater, resources)
+        return DefaultClockController(ctx, layoutInflater, resources, settings)
     }
 
     override fun getClockThumbnail(id: ClockId): Drawable? {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 7f1c78f..c120876 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -178,6 +178,12 @@
         /** Flag denoting whether the customizable clocks feature is enabled. */
         const val FLAG_NAME_CUSTOM_CLOCKS_ENABLED = "is_custom_clocks_feature_enabled"
 
+        /** Flag denoting whether the Wallpaper preview should use the full screen UI. */
+        const val FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW = "wallpaper_fullscreen_preview"
+
+        /** Flag denoting whether the Monochromatic Theme is enabled. */
+        const val FLAG_NAME_MONOCHROMATIC_THEME = "is_monochromatic_theme_enabled"
+
         object Columns {
             /** String. Unique ID for the flag. */
             const val NAME = "name"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
index 18e8a96..bf922bc 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
@@ -21,4 +21,5 @@
     const val MESSAGE_ID_SLOT_SELECTED = 1337
     const val KEY_SLOT_ID = "slot_id"
     const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+    const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances"
 }
diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md
index 8917013..9b4c21e 100644
--- a/packages/SystemUI/docs/dagger.md
+++ b/packages/SystemUI/docs/dagger.md
@@ -8,105 +8,110 @@
 
  - [User's guide](https://google.github.io/dagger/users-guide)
 
-TODO: Add some links.
-
 ## State of the world
 
-Dagger 2 has been turned on for SystemUI and a early first pass has been taken
-for converting everything in [Dependency.java](packages/systemui/src/com/android/systemui/Dependency.java)
-to use Dagger. Since a lot of SystemUI depends on Dependency, stubs have been added to Dependency 
-to proxy any gets through to the instances provided by dagger, this will allow migration of SystemUI 
-through a number of CLs.
+Dagger 2 has been turned on for SystemUI and much of
+[Dependency.java](../src/com/android/systemui/Dependency.java)
+has been converted to use Dagger. Since a lot of SystemUI depends on Dependency,
+stubs have been added to Dependency to proxy any gets through to the instances
+provided by dagger, this will allow migration of SystemUI through a number of CLs.
 
 ### How it works in SystemUI
 
+There are three high level "scopes" of concern in SystemUI. They all represent
+singleton scopes, but serve different purposes.
+
+* `@Singleton` - Instances that are shared everywhere. There isn't a  lot of
+   code in this scope. Things like the main thread, and Android Framework
+   provided instances mostly.
+* `@WMShell` - WindowManager related code in the SystemUI process. We don't
+   want this code relying on the rest of SystemUI, and we don't want the rest
+   of SystemUI peeking into its internals, so it runs in its own Subcomponent.
+* `@SysUISingleton` - Most of what would be considered "SystemUI". Most feature
+   work by SystemUI developers goes into this scope. Useful interfaces from
+   WindowManager are made available inside this Subcomponent.
+
+The root dagger graph is created by an instance of `SystemUIInitializer`.
+See [README.md](../README.md) for more details.
 For the classes that we're using in Dependency and are switching to dagger, the
 equivalent dagger version is using `@Singleton` and therefore only has one instance.
 To have the single instance span all of SystemUI and be easily accessible for
 other components, there is a single root `@Component` that exists that generates
-these. The component lives in [SystemUIFactory](packages/systemui/src/com/android/systemui/SystemUIFactory.java)
-and is called `SystemUIRootComponent`.
-
-```java
-
-@Singleton
-@Component(modules = {SystemUIFactory.class, DependencyProvider.class, DependencyBinder.class,
-        ContextHolder.class})
-public interface SystemUIRootComponent {
-    @Singleton
-    Dependency.DependencyInjector createDependency();
-}
-```
-
-The root component is composed of root modules, which in turn provide the global singleton 
-dependencies across all of SystemUI.
-
-- `SystemUIFactory` `@Provides` dependencies that need to be overridden by SystemUI
-variants (like other form factors e.g. Car). 
-
-- `DependencyBinder` creates the mapping from interfaces to implementation classes. 
-
-- `DependencyProvider` provides or binds any remaining depedencies required.
-
-### Adding injection to a new SystemUI object
-
-SystemUI object are made injectable by adding an entry in `SystemUIBinder`. SystemUIApplication uses
-information in that file to locate and construct an instance of the requested SystemUI class.
+these. The component lives in
+[ReferenceGlobalRootComponent.java](../src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java).
 
 ### Adding a new injectable object
 
-First tag the constructor with `@Inject`. Also tag it with `@Singleton` if only one
-instance should be created.
+First annotate the constructor with `@Inject`. Also annotate it with
+`@SysUISingleton` if only one instance should be created.
 
-```java
-@Singleton
-public class SomethingController {
-  @Inject
-  public SomethingController(Context context,
-    @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
-      // context and mainHandler will be automatically populated.
-  }
+```kotlin
+@SysUISingleton
+class FeatureStartable
+@Inject
+constructor(
+/* ... */
+) {
+    // ...
 }
 ```
 
-If you have an interface class and an implementation class, dagger needs to know
-how to map it. The simplest way to do this is to add an `@Provides` method to
-DependencyProvider. The type of the return value tells dagger which dependency it's providing.
+If you have an interface class and an implementation class, Dagger needs to
+know how to map it. The simplest way to do this is to add an `@Binds` method
+in a module. The type of the return value tells dagger which dependency it's
+providing:
 
-```java
-public class DependencyProvider {
-  //...
-  @Singleton
-  @Provides
-  public SomethingController provideSomethingController(Context context,
-      @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
-    return new SomethingControllerImpl(context, mainHandler);
-  }
+```kotlin
+@Module
+abstract class FeatureModule {
+    @Binds
+    abstract fun bindsFeature(impl: FeatureImpl): Feature
 }
 ```
 
-If you need to access this from Dependency#get, then add an adapter to Dependency
-that maps to the instance provided by Dagger. The changes should be similar
-to the following diff.
+If you have a class that you want to make injectable that has can not
+be easily constructed by Dagger, write a `@Provides` method for it:
 
-```java
-public class Dependency {
-  //...
-  @Inject Lazy<SomethingController> mSomethingController;
-  //...
-  public void start() {
-    //...
-    mProviders.put(SomethingController.class, mSomethingController::get);
-  }
+```kotlin
+@Module
+abstract class FeatureModule {
+    @Module
+    companion object {
+        @Provides
+        fun providesFeature(ctx: Context): Feature {
+            return FeatureImpl.constructFromContext(ctx)
+        }
+    }
 }
 ```
 
+### Module Organization
+
+Please define your modules on _at least_ per-package level. If the scope of a
+package grows to encompass a great number of features, create per-feature
+modules.
+
+**Do not create catch-all modules.** Those quickly grow unwieldy and
+unmaintainable. Any that exist today should be refactored into obsolescence.
+
+You can then include your module in one of three places:
+
+1) Within another module that depends on it. Ideally, this creates a clean
+   dependency graph between features and utilities.
+2) For features that should exist in all versions of SystemUI (AOSP and
+   any variants), include the module in
+   [SystemUIModule.java](../src/com/android/systemui/dagger/SystemUIModule.java).
+3) For features that should exist only in AOSP, include the module in
+   [ReferenceSystemUIModule.java](../src/com/android/systemui/dagger/ReferenceSystemUIModule.java).
+   Similarly, if you are working on a custom version of SystemUI and have code
+   specific to your version, include it in a module specific to your version.
+
 ### Using injection with Fragments
 
 Fragments are created as part of the FragmentManager, so they need to be
 setup so the manager knows how to create them. To do that, add a method
 to com.android.systemui.fragments.FragmentService$FragmentCreator that
-returns your fragment class. Thats all thats required, once the method
+returns your fragment class. That is all that is required, once the method
 exists, FragmentService will automatically pick it up and use injection
 whenever your fragment needs to be created.
 
@@ -123,48 +128,11 @@
 FragmentHostManager.get(view).create(NavigationBarFragment.class);
 ```
 
-### Using injection with Views
-
-DO NOT ADD NEW VIEW INJECTION. VIEW INJECTION IS BEING ACTIVELY DEPRECATED.
-
-Needing to inject objects into your View's constructor generally implies you
-are doing more work in your presentation layer than is advisable.
-Instead, create an injected controller for you view, inject into the
-controller, and then attach the view to the controller after inflation.
-
-View injection generally causes headaches while testing, as inflating a view
-(which may in turn inflate other views) implicitly causes a Dagger graph to 
-be stood up, which may or may not contain the appropriately 
-faked/mocked/stubbed objects. It is a hard to control process.
-
 ## Updating Dagger2
 
 We depend on the Dagger source found in external/dagger2. We should automatically pick up on updates
 when that repository is updated.
-
-*Deprecated:*
-
-Binaries can be downloaded from https://repo1.maven.org/maven2/com/google/dagger/ and then loaded
-into
-[/prebuilts/tools/common/m2/repository/com/google/dagger/](http://cs/android/prebuilts/tools/common/m2/repository/com/google/dagger/)
-
-The following commands should work, substituting in the version that you are looking for:
-
-````
-cd prebuilts/tools/common/m2/repository/com/google/dagger/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-spi/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.28.1/
-````
-
-Then update `prebuilts/tools/common/m2/Android.bp` to point at your new jars.
  
 ## TODO List
 
- - Eliminate usages of Dependency#get
- - Add links in above TODO
+ - Eliminate usages of Dependency#get: http://b/hotlists/3940788
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index ccb35fa..79d5718 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -52,6 +52,10 @@
 * Unselect an already-selected quick affordance from a slot
 * Unselect all already-selected quick affordances from a slot
 
+## Testing
+* Add a unit test for your implementation of `KeyguardQuickAffordanceConfig`
+* Manually verify that your implementation works in multi-user environments from both the main user and a secondary user
+
 ## Debugging
 To see the current state of the system, you can run `dumpsys`:
 
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index f53e3f6..1a67691e 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -1,8 +1,5 @@
 +packages/SystemUI
--packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
 -packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
 -packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
 -packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
 -packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
@@ -200,13 +197,10 @@
 -packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
 -packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt
 -packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
 -packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -322,8 +316,8 @@
 -packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
+-packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -614,7 +608,6 @@
 -packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index dee0f5c..314c736 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -80,6 +80,7 @@
 internal class HueVibrantSecondary() : Hue {
     val hueToRotations = listOf(Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12),
             Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12), Pair(360, 12))
+
     override fun get(sourceColor: Cam): Double {
         return getHueRotation(sourceColor.hue, hueToRotations)
     }
@@ -88,6 +89,7 @@
 internal class HueVibrantTertiary() : Hue {
     val hueToRotations = listOf(Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25),
             Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25), Pair(360, 25))
+
     override fun get(sourceColor: Cam): Double {
         return getHueRotation(sourceColor.hue, hueToRotations)
     }
@@ -96,6 +98,7 @@
 internal class HueExpressiveSecondary() : Hue {
     val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20),
             Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45))
+
     override fun get(sourceColor: Cam): Double {
         return getHueRotation(sourceColor.hue, hueToRotations)
     }
@@ -104,6 +107,7 @@
 internal class HueExpressiveTertiary() : Hue {
     val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45),
             Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120))
+
     override fun get(sourceColor: Cam): Double {
         return getHueRotation(sourceColor.hue, hueToRotations)
     }
@@ -148,11 +152,11 @@
 }
 
 internal class CoreSpec(
-    val a1: TonalSpec,
-    val a2: TonalSpec,
-    val a3: TonalSpec,
-    val n1: TonalSpec,
-    val n2: TonalSpec
+        val a1: TonalSpec,
+        val a2: TonalSpec,
+        val a3: TonalSpec,
+        val n1: TonalSpec,
+        val n2: TonalSpec
 )
 
 enum class Style(internal val coreSpec: CoreSpec) {
@@ -214,51 +218,86 @@
     )),
 }
 
+class TonalPalette {
+    val shadeKeys = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
+    val allShades: List<Int>
+    val allShadesMapped: Map<Int, Int>
+    val baseColor: Int
+
+    internal constructor(spec: TonalSpec, seedColor: Int) {
+        val seedCam = Cam.fromInt(seedColor)
+        allShades = spec.shades(seedCam)
+        allShadesMapped = shadeKeys.zip(allShades).toMap()
+
+        val h = spec.hue.get(seedCam).toFloat()
+        val c = spec.chroma.get(seedCam).toFloat()
+        baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor))
+    }
+
+    val s10: Int get() = this.allShades[0]
+    val s50: Int get() = this.allShades[1]
+    val s100: Int get() = this.allShades[2]
+    val s200: Int get() = this.allShades[3]
+    val s300: Int get() = this.allShades[4]
+    val s400: Int get() = this.allShades[5]
+    val s500: Int get() = this.allShades[6]
+    val s600: Int get() = this.allShades[7]
+    val s700: Int get() = this.allShades[8]
+    val s800: Int get() = this.allShades[9]
+    val s900: Int get() = this.allShades[10]
+    val s1000: Int get() = this.allShades[11]
+}
+
 class ColorScheme(
-    @ColorInt val seed: Int,
-    val darkTheme: Boolean,
-    val style: Style = Style.TONAL_SPOT
+        @ColorInt val seed: Int,
+        val darkTheme: Boolean,
+        val style: Style = Style.TONAL_SPOT
 ) {
 
-    val accent1: List<Int>
-    val accent2: List<Int>
-    val accent3: List<Int>
-    val neutral1: List<Int>
-    val neutral2: List<Int>
+    val accent1: TonalPalette
+    val accent2: TonalPalette
+    val accent3: TonalPalette
+    val neutral1: TonalPalette
+    val neutral2: TonalPalette
 
     constructor(@ColorInt seed: Int, darkTheme: Boolean) :
             this(seed, darkTheme, Style.TONAL_SPOT)
 
     @JvmOverloads
     constructor(
-        wallpaperColors: WallpaperColors,
-        darkTheme: Boolean,
-        style: Style = Style.TONAL_SPOT
+            wallpaperColors: WallpaperColors,
+            darkTheme: Boolean,
+            style: Style = Style.TONAL_SPOT
     ) :
             this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
 
+    val allHues: List<TonalPalette>
+        get() {
+            return listOf(accent1, accent2, accent3, neutral1, neutral2)
+        }
+
     val allAccentColors: List<Int>
         get() {
             val allColors = mutableListOf<Int>()
-            allColors.addAll(accent1)
-            allColors.addAll(accent2)
-            allColors.addAll(accent3)
+            allColors.addAll(accent1.allShades)
+            allColors.addAll(accent2.allShades)
+            allColors.addAll(accent3.allShades)
             return allColors
         }
 
     val allNeutralColors: List<Int>
         get() {
             val allColors = mutableListOf<Int>()
-            allColors.addAll(neutral1)
-            allColors.addAll(neutral2)
+            allColors.addAll(neutral1.allShades)
+            allColors.addAll(neutral2.allShades)
             return allColors
         }
 
     val backgroundColor
-        get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1[8] else neutral1[0], 0xFF)
+        get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1.s700 else neutral1.s10, 0xFF)
 
     val accentColor
-        get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1[2] else accent1[6], 0xFF)
+        get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1.s100 else accent1.s500, 0xFF)
 
     init {
         val proposedSeedCam = Cam.fromInt(seed)
@@ -269,24 +308,26 @@
         } else {
             seed
         }
-        val camSeed = Cam.fromInt(seedArgb)
-        accent1 = style.coreSpec.a1.shades(camSeed)
-        accent2 = style.coreSpec.a2.shades(camSeed)
-        accent3 = style.coreSpec.a3.shades(camSeed)
-        neutral1 = style.coreSpec.n1.shades(camSeed)
-        neutral2 = style.coreSpec.n2.shades(camSeed)
+
+        accent1 = TonalPalette(style.coreSpec.a1, seedArgb)
+        accent2 = TonalPalette(style.coreSpec.a2, seedArgb)
+        accent3 = TonalPalette(style.coreSpec.a3, seedArgb)
+        neutral1 = TonalPalette(style.coreSpec.n1, seedArgb)
+        neutral2 = TonalPalette(style.coreSpec.n2, seedArgb)
     }
 
+    val shadeCount get() = this.accent1.allShades.size
+
     override fun toString(): String {
         return "ColorScheme {\n" +
                 "  seed color: ${stringForColor(seed)}\n" +
                 "  style: $style\n" +
                 "  palettes: \n" +
-                "  ${humanReadable("PRIMARY", accent1)}\n" +
-                "  ${humanReadable("SECONDARY", accent2)}\n" +
-                "  ${humanReadable("TERTIARY", accent3)}\n" +
-                "  ${humanReadable("NEUTRAL", neutral1)}\n" +
-                "  ${humanReadable("NEUTRAL VARIANT", neutral2)}\n" +
+                "  ${humanReadable("PRIMARY", accent1.allShades)}\n" +
+                "  ${humanReadable("SECONDARY", accent2.allShades)}\n" +
+                "  ${humanReadable("TERTIARY", accent3.allShades)}\n" +
+                "  ${humanReadable("NEUTRAL", neutral1.allShades)}\n" +
+                "  ${humanReadable("NEUTRAL VARIANT", neutral2.allShades)}\n" +
                 "}"
     }
 
@@ -385,7 +426,8 @@
                     val existingSeedNearby = seeds.find {
                         val hueA = intToCam[int]!!.hue
                         val hueB = intToCam[it]!!.hue
-                        hueDiff(hueA, hueB) < i } != null
+                        hueDiff(hueA, hueB) < i
+                    } != null
                     if (existingSeedNearby) {
                         continue
                     }
@@ -460,9 +502,9 @@
         }
 
         private fun huePopulations(
-            camByColor: Map<Int, Cam>,
-            populationByColor: Map<Int, Double>,
-            filter: Boolean = true
+                camByColor: Map<Int, Cam>,
+                populationByColor: Map<Int, Double>,
+                filter: Boolean = true
         ): List<Double> {
             val huePopulation = List(size = 360, init = { 0.0 }).toMutableList()
 
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 7709f21..fb1c454 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -29,6 +29,7 @@
         "src/**/*.java",
         "src/**/*.kt",
         "bcsmartspace/src/**/*.java",
+        "bcsmartspace/src/**/*.kt",
     ],
 
     static_libs: [
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt
new file mode 100644
index 0000000..509f022
--- /dev/null
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceConfigPlugin.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.systemui.plugins
+
+// TODO(b/265360975): Evaluate this plugin approach.
+/** Plugin to provide BC smartspace configuration */
+interface BcSmartspaceConfigPlugin {
+    /** Gets default date/weather disabled status. */
+    val isDefaultDateWeatherDisabled: Boolean
+}
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 51f5baa..e0cc8f4 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -50,16 +50,24 @@
     String TAG = "BcSmartspaceDataPlugin";
 
     /** Register a listener to get Smartspace data. */
-    void registerListener(SmartspaceTargetListener listener);
+    default void registerListener(SmartspaceTargetListener listener) {
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
+    }
 
     /** Unregister a listener. */
-    void unregisterListener(SmartspaceTargetListener listener);
+    default void unregisterListener(SmartspaceTargetListener listener) {
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
+    }
 
     /** Register a SmartspaceEventNotifier. */
-    default void registerSmartspaceEventNotifier(SmartspaceEventNotifier notifier) {}
+    default void registerSmartspaceEventNotifier(SmartspaceEventNotifier notifier) {
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
+    }
 
     /** Push a SmartspaceTargetEvent to the SmartspaceEventNotifier. */
-    default void notifySmartspaceEvent(SmartspaceTargetEvent event) {}
+    default void notifySmartspaceEvent(SmartspaceTargetEvent event) {
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
+    }
 
     /** Allows for notifying the SmartspaceSession of SmartspaceTargetEvents. */
     interface SmartspaceEventNotifier {
@@ -72,16 +80,20 @@
      * will be responsible for correctly setting the LayoutParams
      */
     default SmartspaceView getView(ViewGroup parent) {
-        return null;
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
     }
 
     /**
      * As the smartspace view becomes available, allow listeners to receive an event.
      */
-    default void addOnAttachStateChangeListener(View.OnAttachStateChangeListener listener) { }
+    default void addOnAttachStateChangeListener(View.OnAttachStateChangeListener listener) {
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
+    }
 
     /** Updates Smartspace data and propagates it to any listeners. */
-    void onTargetsAvailable(List<SmartspaceTarget> targets);
+    default void onTargetsAvailable(List<SmartspaceTarget> targets) {
+        throw new UnsupportedOperationException("Not implemented by " + getClass());
+    }
 
     /** Provides Smartspace data to registered listeners. */
     interface SmartspaceTargetListener {
@@ -94,6 +106,13 @@
         void registerDataProvider(BcSmartspaceDataPlugin plugin);
 
         /**
+         * Sets {@link BcSmartspaceConfigPlugin}.
+         */
+        default void registerConfigProvider(BcSmartspaceConfigPlugin configProvider) {
+            throw new UnsupportedOperationException("Not implemented by " + getClass());
+        }
+
+        /**
          * Primary color for unprotected text
          */
         void setPrimaryTextColor(int color);
@@ -133,28 +152,38 @@
         /**
          * Set or clear Do Not Disturb information.
          */
-        void setDnd(@Nullable Drawable image, @Nullable String description);
+        default void setDnd(@Nullable Drawable image, @Nullable String description) {
+            throw new UnsupportedOperationException("Not implemented by " + getClass());
+        }
 
         /**
          * Set or clear next alarm information
          */
-        void setNextAlarm(@Nullable Drawable image, @Nullable String description);
+        default void setNextAlarm(@Nullable Drawable image, @Nullable String description) {
+            throw new UnsupportedOperationException("Not implemented by " + getClass());
+        }
 
         /**
          * Set or clear device media playing
          */
-        void setMediaTarget(@Nullable SmartspaceTarget target);
+        default void setMediaTarget(@Nullable SmartspaceTarget target) {
+            throw new UnsupportedOperationException("Not implemented by " + getClass());
+        }
 
         /**
          * Get the index of the currently selected page.
          */
-        int getSelectedPage();
+        default int getSelectedPage() {
+            throw new UnsupportedOperationException("Not implemented by " + getClass());
+        }
 
         /**
          * Return the top padding value from the currently visible card, or 0 if there is no current
          * card.
          */
-        int getCurrentCardTopPadding();
+        default int getCurrentCardTopPadding() {
+            throw new UnsupportedOperationException("Not implemented by " + getClass());
+        }
     }
 
     /** Interface for launching Intents, which can differ on the lockscreen */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index a2a0709..4ef525a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -17,11 +17,14 @@
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.view.View
+import com.android.internal.annotations.Keep
 import com.android.systemui.plugins.annotations.ProvidesInterface
 import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.statusbar.Weather
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
+import org.json.JSONObject
 
 /** Identifies a clock design */
 typealias ClockId = String
@@ -41,7 +44,13 @@
     fun getClocks(): List<ClockMetadata>
 
     /** Initializes and returns the target clock design */
-    fun createClock(id: ClockId): ClockController
+    @Deprecated("Use overload with ClockSettings")
+    fun createClock(id: ClockId): ClockController {
+        return createClock(ClockSettings(id, null, null))
+    }
+
+    /** Initializes and returns the target clock design */
+    fun createClock(settings: ClockSettings): ClockController
 
     /** A static thumbnail for rendering in some examples */
     fun getClockThumbnail(id: ClockId): Drawable?
@@ -62,11 +71,16 @@
     val animations: ClockAnimations
 
     /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
-    fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+    fun initialize(
+        resources: Resources,
+        dozeFraction: Float,
+        foldFraction: Float,
+    ) {
         events.onColorPaletteChanged(resources)
         animations.doze(dozeFraction)
         animations.fold(foldFraction)
-        events.onTimeTick()
+        smallClock.events.onTimeTick()
+        largeClock.events.onTimeTick()
     }
 
     /** Optional method for dumping debug information */
@@ -87,9 +101,6 @@
 
 /** Events that should call when various rendering parameters change */
 interface ClockEvents {
-    /** Call every time tick */
-    fun onTimeTick() {}
-
     /** Call whenever timezone changes */
     fun onTimeZoneChanged(timeZone: TimeZone) {}
 
@@ -101,6 +112,9 @@
 
     /** Call whenever the color palette should update */
     fun onColorPaletteChanged(resources: Resources) {}
+
+    /** Call whenever the weather data should update */
+    fun onWeatherDataChanged(data: Weather) {}
 }
 
 /** Methods which trigger various clock animations */
@@ -131,6 +145,13 @@
 
 /** Events that have specific data about the related face */
 interface ClockFaceEvents {
+    /** Call every time tick */
+    fun onTimeTick() {}
+
+    /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
+    val tickRate: ClockTickRate
+        get() = ClockTickRate.PER_MINUTE
+
     /** Region Darkness specific to the clock face */
     fun onRegionDarknessChanged(isDark: Boolean) {}
 
@@ -150,8 +171,46 @@
     fun onTargetRegionChanged(targetRegion: Rect?) {}
 }
 
+/** Tick rates for clocks */
+enum class ClockTickRate(val value: Int) {
+    PER_MINUTE(2), // Update the clock once per minute.
+    PER_SECOND(1), // Update the clock once per second.
+    PER_FRAME(0), // Update the clock every second.
+}
+
 /** Some data about a clock design */
 data class ClockMetadata(
     val clockId: ClockId,
     val name: String,
 )
+
+/** Structure for keeping clock-specific settings */
+@Keep
+data class ClockSettings(
+    var clockId: ClockId? = null,
+    var seedColor: Int? = null,
+    var _applied_timestamp: Long? = null,
+) {
+    companion object {
+        private val KEY_CLOCK_ID = "clockId"
+        private val KEY_SEED_COLOR = "seedColor"
+        private val KEY_TIMESTAMP = "_applied_timestamp"
+
+        fun serialize(setting: ClockSettings): String {
+            return JSONObject()
+                .put(KEY_CLOCK_ID, setting.clockId)
+                .put(KEY_SEED_COLOR, setting.seedColor)
+                .put(KEY_TIMESTAMP, setting._applied_timestamp)
+                .toString()
+        }
+
+        fun deserialize(jsonStr: String): ClockSettings {
+            val json = JSONObject(jsonStr)
+            return ClockSettings(
+                json.getString(KEY_CLOCK_ID),
+                if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null,
+                if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
new file mode 100644
index 0000000..85ec42d
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
@@ -0,0 +1,85 @@
+package com.android.systemui.statusbar
+
+import android.os.Bundle
+
+class Weather(val conditions: WeatherStateIcon, val temperature: Int, val isCelsius: Boolean) {
+    companion object {
+        private const val TAG = "Weather"
+        private const val WEATHER_STATE_ICON_KEY = "weather_state_icon_extra_key"
+        private const val TEMPERATURE_VALUE_KEY = "temperature_value_extra_key"
+        private const val TEMPERATURE_UNIT_KEY = "temperature_unit_extra_key"
+        private const val INVALID_TEMPERATURE = Int.MIN_VALUE
+
+        fun fromBundle(extras: Bundle): Weather? {
+            val icon =
+                WeatherStateIcon.fromInt(
+                    extras.getInt(WEATHER_STATE_ICON_KEY, WeatherStateIcon.UNKNOWN_ICON.id)
+                )
+            if (icon == null || icon == WeatherStateIcon.UNKNOWN_ICON) {
+                return null
+            }
+            val temperature = extras.getInt(TEMPERATURE_VALUE_KEY, INVALID_TEMPERATURE)
+            if (temperature == INVALID_TEMPERATURE) {
+                return null
+            }
+            return Weather(icon, temperature, extras.getBoolean(TEMPERATURE_UNIT_KEY))
+        }
+    }
+
+    enum class WeatherStateIcon(val id: Int) {
+        UNKNOWN_ICON(0),
+
+        // Clear, day & night.
+        SUNNY(1),
+        CLEAR_NIGHT(2),
+
+        // Mostly clear, day & night.
+        MOSTLY_SUNNY(3),
+        MOSTLY_CLEAR_NIGHT(4),
+
+        // Partly cloudy, day & night.
+        PARTLY_CLOUDY(5),
+        PARTLY_CLOUDY_NIGHT(6),
+
+        // Mostly cloudy, day & night.
+        MOSTLY_CLOUDY_DAY(7),
+        MOSTLY_CLOUDY_NIGHT(8),
+        CLOUDY(9),
+        HAZE_FOG_DUST_SMOKE(10),
+        DRIZZLE(11),
+        HEAVY_RAIN(12),
+        SHOWERS_RAIN(13),
+
+        // Scattered showers, day & night.
+        SCATTERED_SHOWERS_DAY(14),
+        SCATTERED_SHOWERS_NIGHT(15),
+
+        // Isolated scattered thunderstorms, day & night.
+        ISOLATED_SCATTERED_TSTORMS_DAY(16),
+        ISOLATED_SCATTERED_TSTORMS_NIGHT(17),
+        STRONG_TSTORMS(18),
+        BLIZZARD(19),
+        BLOWING_SNOW(20),
+        FLURRIES(21),
+        HEAVY_SNOW(22),
+
+        // Scattered snow showers, day & night.
+        SCATTERED_SNOW_SHOWERS_DAY(23),
+        SCATTERED_SNOW_SHOWERS_NIGHT(24),
+        SNOW_SHOWERS_SNOW(25),
+        MIXED_RAIN_HAIL_RAIN_SLEET(26),
+        SLEET_HAIL(27),
+        TORNADO(28),
+        TROPICAL_STORM_HURRICANE(29),
+        WINDY_BREEZY(30),
+        WINTRY_MIX_RAIN_SNOW(31);
+
+        companion object {
+            fun fromInt(value: Int) = values().firstOrNull { it.id == value }
+        }
+    }
+
+    override fun toString(): String {
+        return "$conditions $temperature${if (isCelsius) "C" else "F"}"
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
index d3fabac..faf1b78 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogcatEchoTrackerDebug.kt
@@ -21,6 +21,7 @@
 import android.net.Uri
 import android.os.Handler
 import android.os.Looper
+import android.os.Trace
 import android.provider.Settings
 
 /**
@@ -51,14 +52,21 @@
         }
     }
 
+    private fun clearCache() {
+        Trace.beginSection("LogcatEchoTrackerDebug#clearCache")
+        cachedBufferLevels.clear()
+        Trace.endSection()
+    }
+
     private fun attach(mainLooper: Looper) {
+        Trace.beginSection("LogcatEchoTrackerDebug#attach")
         contentResolver.registerContentObserver(
             Settings.Global.getUriFor(BUFFER_PATH),
             true,
             object : ContentObserver(Handler(mainLooper)) {
                 override fun onChange(selfChange: Boolean, uri: Uri?) {
                     super.onChange(selfChange, uri)
-                    cachedBufferLevels.clear()
+                    clearCache()
                 }
             }
         )
@@ -69,10 +77,11 @@
             object : ContentObserver(Handler(mainLooper)) {
                 override fun onChange(selfChange: Boolean, uri: Uri?) {
                     super.onChange(selfChange, uri)
-                    cachedTagLevels.clear()
+                    clearCache()
                 }
             }
         )
+        Trace.endSection()
     }
 
     /** Whether [bufferName] should echo messages of [level] or higher to logcat. */
@@ -97,9 +106,12 @@
 
     private fun readSetting(path: String): LogLevel {
         return try {
+            Trace.beginSection("LogcatEchoTrackerDebug#readSetting")
             parseProp(Settings.Global.getString(contentResolver, path))
         } catch (_: Settings.SettingNotFoundException) {
             DEFAULT_LEVEL
+        } finally {
+            Trace.endSection()
         }
     }
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 9ed3bac..70b5d73 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -105,6 +105,11 @@
         default void onDozingChanged(boolean isDozing) {}
 
         /**
+         * Callback to be notified when Dreaming changes. Dreaming is stored separately from state.
+         */
+        default void onDreamingChanged(boolean isDreaming) {}
+
+        /**
          * Callback to be notified when the doze amount changes. Useful for animations.
          * Note: this will be called for each animation frame. Please be careful to avoid
          * performance regressions.
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 2cac9c7..90851e2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -45,7 +45,7 @@
           android:id="@+id/user_switcher_header"
           android:textDirection="locale"
           android:layout_width="@dimen/bouncer_user_switcher_width"
-          android:layout_height="wrap_content" />
+          android:layout_height="match_parent" />
     </com.android.keyguard.KeyguardUserSwitcherAnchor>
 
 </LinearLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 64ece47..ca4028a 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -105,6 +105,7 @@
             android:id="@+id/key1"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
             androidprv:digit="1"
             androidprv:textView="@+id/pinEntry" />
 
@@ -112,6 +113,7 @@
             android:id="@+id/key2"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
             androidprv:digit="2"
             androidprv:textView="@+id/pinEntry" />
 
@@ -119,6 +121,7 @@
             android:id="@+id/key3"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
             androidprv:digit="3"
             androidprv:textView="@+id/pinEntry" />
 
@@ -126,6 +129,7 @@
             android:id="@+id/key4"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
             androidprv:digit="4"
             androidprv:textView="@+id/pinEntry" />
 
@@ -133,6 +137,7 @@
             android:id="@+id/key5"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
             androidprv:digit="5"
             androidprv:textView="@+id/pinEntry" />
 
@@ -140,6 +145,7 @@
             android:id="@+id/key6"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
             androidprv:digit="6"
             androidprv:textView="@+id/pinEntry" />
 
@@ -147,13 +153,16 @@
             android:id="@+id/key7"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
             androidprv:digit="7"
             androidprv:textView="@+id/pinEntry" />
 
+
         <com.android.keyguard.NumPadKey
             android:id="@+id/key8"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
             androidprv:digit="8"
             androidprv:textView="@+id/pinEntry" />
 
@@ -161,34 +170,33 @@
             android:id="@+id/key9"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
             androidprv:digit="9"
             androidprv:textView="@+id/pinEntry" />
 
-
         <com.android.keyguard.NumPadButton
             android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
             android:layout_width="0dp"
             android:layout_height="0dp"
-            style="@style/NumPadKey.Delete"
-            android:contentDescription="@string/keyboardview_keycode_delete"
-            />
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
 
         <com.android.keyguard.NumPadKey
             android:id="@+id/key0"
             android:layout_width="0dp"
             android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
             androidprv:digit="0"
             androidprv:textView="@+id/pinEntry" />
 
         <com.android.keyguard.NumPadButton
             android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
             android:layout_width="0dp"
             android:layout_height="0dp"
-            style="@style/NumPadKey.Enter"
-            android:contentDescription="@string/keyboardview_keycode_enter"
-            />
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+</androidx.constraintlayout.widget.ConstraintLayout>
 
     <include layout="@layout/keyguard_eca"
              android:id="@+id/keyguard_selector_fade_container"
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
index a1068c6..6c8db91 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
@@ -25,9 +25,6 @@
     <!-- Margin around the various security views -->
     <dimen name="keyguard_security_view_top_margin">12dp</dimen>
 
-    <!-- Padding for the lock icon on the keyguard -->
-    <dimen name="lock_icon_padding">16dp</dimen>
-
     <!-- Overload default clock widget parameters -->
     <dimen name="widget_big_font_size">100dp</dimen>
     <dimen name="widget_label_font_size">18sp</dimen>
diff --git a/packages/SystemUI/res-product/values-de/strings.xml b/packages/SystemUI/res-product/values-de/strings.xml
index b8d1a7c..c82c739 100644
--- a/packages/SystemUI/res-product/values-de/strings.xml
+++ b/packages/SystemUI/res-product/values-de/strings.xml
@@ -43,6 +43,6 @@
     <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Entsperre dein Smartphone für weitere Optionen"</string>
     <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Entsperre dein Tablet für weitere Optionen"</string>
     <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Entsperre dein Gerät für weitere Optionen"</string>
-    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Wird auf diesem Smartphone abgespielt"</string>
-    <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Wird auf diesem Tablet abgespielt"</string>
+    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Wiedergabe läuft auf diesem Smartphone"</string>
+    <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Wiedergabe läuft auf diesem Tablet"</string>
 </resources>
diff --git a/packages/SystemUI/res-product/values-fr/strings.xml b/packages/SystemUI/res-product/values-fr/strings.xml
index a07c8d8..75a0862 100644
--- a/packages/SystemUI/res-product/values-fr/strings.xml
+++ b/packages/SystemUI/res-product/values-fr/strings.xml
@@ -43,6 +43,6 @@
     <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Déverrouillez votre téléphone pour obtenir plus d\'options"</string>
     <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Déverrouillez votre tablette pour obtenir plus d\'options"</string>
     <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Déverrouillez votre appareil pour obtenir plus d\'options"</string>
-    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Lecture sur ce téléphone…"</string>
+    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Lecture sur ce téléphone"</string>
     <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Lecture sur cette tablette…"</string>
 </resources>
diff --git a/packages/SystemUI/res-product/values-ka/strings.xml b/packages/SystemUI/res-product/values-ka/strings.xml
index 1b496cc..3474a7f 100644
--- a/packages/SystemUI/res-product/values-ka/strings.xml
+++ b/packages/SystemUI/res-product/values-ka/strings.xml
@@ -43,6 +43,6 @@
     <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"მეტი ვარიანტის სანახავად განბლოკეთ თქვენი ტელეფონი"</string>
     <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"მეტი ვარიანტის სანახავად განბლოკეთ თქვენი ტაბლეტი"</string>
     <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"მეტი ვარიანტის სანახავად განბლოკეთ თქვენი მოწყობილობა"</string>
-    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"უკრავს ტელეფონზე"</string>
+    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"უკრავს ამ ტელეფონზე"</string>
     <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"უკრავს ამ ტაბლეტზე"</string>
 </resources>
diff --git a/packages/SystemUI/res-product/values-pt-rBR/strings.xml b/packages/SystemUI/res-product/values-pt-rBR/strings.xml
index b287fd9..fff7606 100644
--- a/packages/SystemUI/res-product/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-product/values-pt-rBR/strings.xml
@@ -43,6 +43,6 @@
     <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Desbloqueie seu smartphone para ver mais opções"</string>
     <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Desbloqueie seu tablet para ver mais opções"</string>
     <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Desbloqueie seu dispositivo para ver mais opções"</string>
-    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Mídia aberta neste smartphone"</string>
-    <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Mídia aberta neste tablet"</string>
+    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Tocando neste smartphone"</string>
+    <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Mídia tocando neste tablet"</string>
 </resources>
diff --git a/packages/SystemUI/res-product/values-pt/strings.xml b/packages/SystemUI/res-product/values-pt/strings.xml
index b287fd9..fff7606 100644
--- a/packages/SystemUI/res-product/values-pt/strings.xml
+++ b/packages/SystemUI/res-product/values-pt/strings.xml
@@ -43,6 +43,6 @@
     <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Desbloqueie seu smartphone para ver mais opções"</string>
     <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Desbloqueie seu tablet para ver mais opções"</string>
     <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Desbloqueie seu dispositivo para ver mais opções"</string>
-    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Mídia aberta neste smartphone"</string>
-    <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Mídia aberta neste tablet"</string>
+    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Tocando neste smartphone"</string>
+    <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Mídia tocando neste tablet"</string>
 </resources>
diff --git a/packages/SystemUI/res-product/values-uz/strings.xml b/packages/SystemUI/res-product/values-uz/strings.xml
index bf8d94a..58309ff 100644
--- a/packages/SystemUI/res-product/values-uz/strings.xml
+++ b/packages/SystemUI/res-product/values-uz/strings.xml
@@ -43,6 +43,6 @@
     <string name="global_action_lock_message" product="default" msgid="7092460751050168771">"Boshqa parametrlar uchun telefoningiz qulfini oching"</string>
     <string name="global_action_lock_message" product="tablet" msgid="1024230056230539493">"Boshqa parametrlar uchun planshetingiz qulfini oching"</string>
     <string name="global_action_lock_message" product="device" msgid="3165224897120346096">"Boshqa parametrlar uchun qurilmangiz qulfini oching"</string>
-    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Bu telefonda ijro etilmoqda"</string>
+    <string name="media_transfer_playing_this_device" product="default" msgid="5795784619523545556">"Bu telefonda ijro qilinmoqda"</string>
     <string name="media_transfer_playing_this_device" product="tablet" msgid="222514408550408633">"Bu planshetda ijro etilmoqda"</string>
 </resources>
diff --git a/packages/SystemUI/res/drawable/clipboard_minimized_background.xml b/packages/SystemUI/res/drawable/clipboard_minimized_background.xml
new file mode 100644
index 0000000..a179c14
--- /dev/null
+++ b/packages/SystemUI/res/drawable/clipboard_minimized_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <solid android:color="?androidprv:attr/colorAccentSecondary"/>
+    <corners android:radius="10dp"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/ic_camera.xml b/packages/SystemUI/res/drawable/ic_camera.xml
new file mode 100644
index 0000000..ef1406c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_camera.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M7,42Q5.8,42 4.9,41.1Q4,40.2 4,39V13.35Q4,12.15 4.9,11.25Q5.8,10.35 7,10.35H14.35L18,6H30L33.65,10.35H41Q42.2,10.35 43.1,11.25Q44,12.15 44,13.35V39Q44,40.2 43.1,41.1Q42.2,42 41,42ZM7,39H41Q41,39 41,39Q41,39 41,39V13.35Q41,13.35 41,13.35Q41,13.35 41,13.35H7Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM7,39Q7,39 7,39Q7,39 7,39V13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM24,34.7Q27.5,34.7 30,32.225Q32.5,29.75 32.5,26.2Q32.5,22.7 30,20.2Q27.5,17.7 24,17.7Q20.45,17.7 17.975,20.2Q15.5,22.7 15.5,26.2Q15.5,29.75 17.975,32.225Q20.45,34.7 24,34.7ZM24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_content_paste.xml b/packages/SystemUI/res/drawable/ic_content_paste.xml
new file mode 100644
index 0000000..8c8b81e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_content_paste.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48"
+        android:viewportHeight="48"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M9,42Q7.7,42 6.85,41.15Q6,40.3 6,39V9Q6,7.7 6.85,6.85Q7.7,6 9,6H19.1Q19.45,4.25 20.825,3.125Q22.2,2 24,2Q25.8,2 27.175,3.125Q28.55,4.25 28.9,6H39Q40.3,6 41.15,6.85Q42,7.7 42,9V39Q42,40.3 41.15,41.15Q40.3,42 39,42ZM9,39H39Q39,39 39,39Q39,39 39,39V9Q39,9 39,9Q39,9 39,9H36V13.5H12V9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39ZM24,9Q24.85,9 25.425,8.425Q26,7.85 26,7Q26,6.15 25.425,5.575Q24.85,5 24,5Q23.15,5 22.575,5.575Q22,6.15 22,7Q22,7.85 22.575,8.425Q23.15,9 24,9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_note_task_button.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
similarity index 92%
copy from packages/SystemUI/res/drawable/ic_note_task_button.xml
copy to packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
index bb5e224..ee8d4883 100644
--- a/packages/SystemUI/res/drawable/ic_note_task_button.xml
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_keyguard.xml
@@ -19,10 +19,13 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
-        android:fillColor="#636C6F"
+        android:fillAlpha="1"
+        android:fillColor="@android:color/white"
+        android:fillType="nonZero"
         android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
     <path
-        android:fillColor="#636C6F"
-        android:fillType="evenOdd"
+        android:fillAlpha="1"
+        android:fillColor="@android:color/white"
+        android:fillType="nonZero"
         android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_button.xml b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
similarity index 95%
rename from packages/SystemUI/res/drawable/ic_note_task_button.xml
rename to packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
index bb5e224..7590182 100644
--- a/packages/SystemUI/res/drawable/ic_note_task_button.xml
+++ b/packages/SystemUI/res/drawable/ic_note_task_shortcut_widget.xml
@@ -19,10 +19,13 @@
     android:viewportHeight="24"
     android:viewportWidth="24">
     <path
+        android:fillAlpha="1"
         android:fillColor="#636C6F"
+        android:fillType="nonZero"
         android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
     <path
+        android:fillAlpha="1"
         android:fillColor="#636C6F"
-        android:fillType="evenOdd"
+        android:fillType="nonZero"
         android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_videocam.xml b/packages/SystemUI/res/drawable/ic_videocam.xml
new file mode 100644
index 0000000..de2bc7b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_videocam.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H33Q34.2,8 35.1,8.9Q36,9.8 36,11V21.75L44,13.75V34.25L36,26.25V37Q36,38.2 35.1,39.1Q34.2,40 33,40ZM7,37H33Q33,37 33,37Q33,37 33,37V11Q33,11 33,11Q33,11 33,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
index 18fcebb..87b5a4c 100644
--- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
@@ -16,53 +16,13 @@
 * limitations under the License.
 */
 -->
-<selector
+<shape
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-
-  <item android:state_selected="true">
-    <layer-list>
-      <item
-          android:left="3dp"
-          android:top="3dp"
-          android:right="3dp"
-          android:bottom="3dp">
-        <shape android:shape="oval">
-          <solid android:color="?androidprv:attr/colorSurface"/>
-          <size
-              android:width="@dimen/keyguard_affordance_width"
-              android:height="@dimen/keyguard_affordance_height"/>
-        </shape>
-      </item>
-
-      <item>
-        <shape android:shape="oval">
-          <stroke
-              android:color="@color/control_primary_text"
-              android:width="2dp"/>
-          <size
-              android:width="@dimen/keyguard_affordance_width"
-              android:height="@dimen/keyguard_affordance_height"/>
-        </shape>
-      </item>
-    </layer-list>
-  </item>
-
-  <item>
-    <layer-list>
-      <item
-          android:left="3dp"
-          android:top="3dp"
-          android:right="3dp"
-          android:bottom="3dp">
-        <shape android:shape="oval">
-          <solid android:color="?androidprv:attr/colorSurface"/>
-          <size
-              android:width="@dimen/keyguard_affordance_width"
-              android:height="@dimen/keyguard_affordance_height"/>
-        </shape>
-      </item>
-    </layer-list>
-  </item>
-
-</selector>
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+  <solid android:color="?androidprv:attr/colorSurface"/>
+  <size
+      android:width="@dimen/keyguard_affordance_fixed_width"
+      android:height="@dimen/keyguard_affordance_fixed_height"/>
+  <corners android:radius="@dimen/keyguard_affordance_fixed_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml
new file mode 100644
index 0000000..7d03b0d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_selected_border.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 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.
+*/
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+  <item android:state_selected="true">
+    <shape android:shape="oval">
+      <stroke
+          android:color="?android:attr/textColorPrimary"
+          android:width="2dp"/>
+      <size
+          android:width="@dimen/keyguard_affordance_fixed_width"
+          android:height="@dimen/keyguard_affordance_fixed_height"/>
+    </shape>
+  </item>
+</selector>
diff --git a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
new file mode 100644
index 0000000..3807b92
--- /dev/null
+++ b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white"/>
+            <corners android:radius="28dp" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="?androidprv:attr/colorSurface" />
+            <corners android:radius="28dp" />
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index eec3b11..9b01bd8 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -125,6 +125,45 @@
             android:layout_width="@dimen/clipboard_preview_size"
             android:layout_height="@dimen/clipboard_preview_size"/>
     </FrameLayout>
+    <LinearLayout
+        android:id="@+id/minimized_preview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:elevation="7dp"
+        android:padding="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+        android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+        android:background="@drawable/clipboard_minimized_background">
+        <ImageView
+            android:src="@drawable/ic_content_paste"
+            android:tint="?attr/overlayButtonTextColor"
+            android:layout_width="24dp"
+            android:layout_height="24dp"/>
+        <ImageView
+            android:src="@*android:drawable/ic_chevron_end"
+            android:tint="?attr/overlayButtonTextColor"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:paddingEnd="-8dp"
+            android:paddingStart="-4dp"/>
+    </LinearLayout>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_content_top"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:barrierDirection="top"
+        app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
+    <androidx.constraintlayout.widget.Barrier
+        android:id="@+id/clipboard_content_end"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:barrierDirection="end"
+        app:constraint_referenced_ids="clipboard_preview,minimized_preview"/>
     <FrameLayout
         android:id="@+id/dismiss_button"
         android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
@@ -132,10 +171,10 @@
         android:elevation="10dp"
         android:visibility="gone"
         android:alpha="0"
-        app:layout_constraintStart_toEndOf="@id/clipboard_preview"
-        app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
-        app:layout_constraintTop_toTopOf="@id/clipboard_preview"
-        app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
+        app:layout_constraintStart_toEndOf="@id/clipboard_content_end"
+        app:layout_constraintEnd_toEndOf="@id/clipboard_content_end"
+        app:layout_constraintTop_toTopOf="@id/clipboard_content_top"
+        app:layout_constraintBottom_toTopOf="@id/clipboard_content_top"
         android:contentDescription="@string/clipboard_dismiss_description">
         <ImageView
             android:id="@+id/dismiss_image"
diff --git a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml b/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
deleted file mode 100644
index 1a1fc75..0000000
--- a/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml
+++ /dev/null
@@ -1,160 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 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.
-  -->
-<com.android.systemui.screenshot.DraggableConstraintLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/clipboard_ui"
-    android:theme="@style/FloatingOverlay"
-    android:alpha="0"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:contentDescription="@string/clipboard_overlay_window_name">
-    <ImageView
-        android:id="@+id/actions_container_background"
-        android:visibility="gone"
-        android:layout_height="0dp"
-        android:layout_width="0dp"
-        android:elevation="4dp"
-        android:background="@drawable/action_chip_container_background"
-        android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
-        app:layout_constraintBottom_toBottomOf="@+id/actions_container"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/actions_container"
-        app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
-    <HorizontalScrollView
-        android:id="@+id/actions_container"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
-        android:paddingEnd="@dimen/overlay_action_container_padding_right"
-        android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
-        android:elevation="4dp"
-        android:scrollbars="none"
-        android:layout_marginBottom="4dp"
-        app:layout_constraintHorizontal_bias="0"
-        app:layout_constraintWidth_percent="1.0"
-        app:layout_constraintWidth_max="wrap"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toEndOf="@+id/preview_border"
-        app:layout_constraintEnd_toEndOf="parent">
-        <LinearLayout
-            android:id="@+id/actions"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:animateLayoutChanges="true">
-            <include layout="@layout/overlay_action_chip"
-                     android:id="@+id/share_chip"/>
-            <include layout="@layout/overlay_action_chip"
-                     android:id="@+id/remote_copy_chip"/>
-            <include layout="@layout/overlay_action_chip"
-                     android:id="@+id/edit_chip"/>
-        </LinearLayout>
-    </HorizontalScrollView>
-    <View
-        android:id="@+id/preview_border"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_marginStart="@dimen/overlay_offset_x"
-        android:layout_marginBottom="12dp"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        android:elevation="7dp"
-        app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end"
-        app:layout_constraintTop_toTopOf="@id/clipboard_preview_top"
-        android:background="@drawable/overlay_border"/>
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/clipboard_preview_end"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierMargin="@dimen/overlay_border_width"
-        app:barrierDirection="end"
-        app:constraint_referenced_ids="clipboard_preview"/>
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/clipboard_preview_top"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierDirection="top"
-        app:barrierMargin="@dimen/overlay_border_width_neg"
-        app:constraint_referenced_ids="clipboard_preview"/>
-    <FrameLayout
-        android:id="@+id/clipboard_preview"
-        android:elevation="7dp"
-        android:background="@drawable/overlay_preview_background"
-        android:clipChildren="true"
-        android:clipToOutline="true"
-        android:clipToPadding="true"
-        android:layout_width="@dimen/clipboard_preview_size"
-        android:layout_margin="@dimen/overlay_border_width"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        app:layout_constraintBottom_toBottomOf="@id/preview_border"
-        app:layout_constraintStart_toStartOf="@id/preview_border"
-        app:layout_constraintEnd_toEndOf="@id/preview_border"
-        app:layout_constraintTop_toTopOf="@id/preview_border">
-        <TextView android:id="@+id/text_preview"
-                  android:textFontWeight="500"
-                  android:padding="8dp"
-                  android:gravity="center|start"
-                  android:ellipsize="end"
-                  android:autoSizeTextType="uniform"
-                  android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
-                  android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
-                  android:textColor="?attr/overlayButtonTextColor"
-                  android:textColorLink="?attr/overlayButtonTextColor"
-                  android:background="?androidprv:attr/colorAccentSecondary"
-                  android:layout_width="@dimen/clipboard_preview_size"
-                  android:layout_height="@dimen/clipboard_preview_size"/>
-        <ImageView
-            android:id="@+id/image_preview"
-            android:scaleType="fitCenter"
-            android:adjustViewBounds="true"
-            android:contentDescription="@string/clipboard_image_preview"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
-        <TextView
-            android:id="@+id/hidden_preview"
-            android:visibility="gone"
-            android:textFontWeight="500"
-            android:padding="8dp"
-            android:gravity="center"
-            android:textSize="14sp"
-            android:textColor="?attr/overlayButtonTextColor"
-            android:background="?androidprv:attr/colorAccentSecondary"
-            android:layout_width="@dimen/clipboard_preview_size"
-            android:layout_height="@dimen/clipboard_preview_size"/>
-    </FrameLayout>
-    <FrameLayout
-        android:id="@+id/dismiss_button"
-        android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
-        android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
-        android:elevation="10dp"
-        android:visibility="gone"
-        android:alpha="0"
-        app:layout_constraintStart_toEndOf="@id/clipboard_preview"
-        app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
-        app:layout_constraintTop_toTopOf="@id/clipboard_preview"
-        app:layout_constraintBottom_toTopOf="@id/clipboard_preview"
-        android:contentDescription="@string/clipboard_dismiss_description">
-        <ImageView
-            android:id="@+id/dismiss_image"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_margin="@dimen/overlay_dismiss_button_margin"
-            android:src="@drawable/overlay_cancel"/>
-    </FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index ee3adba..aa211bf 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -20,7 +20,6 @@
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="horizontal"
-      android:layout_marginTop="@dimen/controls_top_margin"
       android:layout_marginBottom="@dimen/controls_header_bottom_margin">
 
     <!-- make sure the header stays centered in the layout by adding a spacer -->
@@ -78,6 +77,7 @@
         android:layout_weight="1"
         android:orientation="vertical"
         android:clipChildren="true"
+        android:paddingHorizontal="16dp"
         android:scrollbars="none">
     <include layout="@layout/global_actions_controls_list_view" />
 
@@ -88,10 +88,7 @@
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="1"
-      android:layout_marginLeft="@dimen/global_actions_side_margin"
-      android:layout_marginRight="@dimen/global_actions_side_margin"
       android:background="@drawable/controls_panel_background"
-      android:padding="@dimen/global_actions_side_margin"
       android:visibility="gone"
       />
 </merge>
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index de96e97..446bb01 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -20,7 +20,7 @@
     android:layout_width="wrap_content"
     android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
 
-    <ImageView
+    <com.android.systemui.common.ui.view.LaunchableImageView
         android:id="@+id/home_controls_chip"
         android:layout_height="@dimen/keyguard_affordance_fixed_height"
         android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/layout/emergency_cryptkeeper_text.xml b/packages/SystemUI/res/layout/emergency_cryptkeeper_text.xml
deleted file mode 100644
index 0a1730a..0000000
--- a/packages/SystemUI/res/layout/emergency_cryptkeeper_text.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2016 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
-  -->
-
-<com.android.systemui.statusbar.policy.EmergencyCryptkeeperText
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/emergency_cryptkeeper_text"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:textAppearance="@style/TextAppearance.StatusBar.Clock"
-        android:paddingStart="6dp"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:gravity="center_vertical|start"
-        />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_grid_lite.xml b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
index 5588fd3..a64c9ae 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_lite.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_lite.xml
@@ -33,7 +33,7 @@
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       android:layout_weight="1">
-    <androidx.constraintlayout.widget.ConstraintLayout
+    <com.android.systemui.common.ui.view.LaunchableConstraintLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@android:id/list"
@@ -55,6 +55,6 @@
           app:flow_horizontalGap="@dimen/global_actions_lite_padding"
           app:flow_verticalGap="@dimen/global_actions_lite_padding"
           app:flow_horizontalStyle="packed"/>
-    </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.android.systemui.common.ui.view.LaunchableConstraintLayout>
   </com.android.systemui.globalactions.GlobalActionsLayoutLite>
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 6120863..3f95515 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -67,6 +67,7 @@
         android:scaleType="center"
         android:tint="?android:attr/textColorPrimary"
         android:background="@drawable/keyguard_bottom_affordance_bg"
+        android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
         android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
         android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
         android:visibility="gone" />
@@ -79,6 +80,7 @@
         android:scaleType="center"
         android:tint="?android:attr/textColorPrimary"
         android:background="@drawable/keyguard_bottom_affordance_bg"
+        android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
         android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
         android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
         android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index 6f33623..07c428b 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -24,7 +24,7 @@
     android:layout_gravity="end">
     <!-- We add a background behind the UserAvatarView with the same color and with a circular shape
          so that this view can be expanded into a Dialog or an Activity. -->
-    <FrameLayout
+    <com.android.systemui.animation.LaunchableFrameLayout
         android:id="@+id/kg_multi_user_avatar_with_background"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -42,5 +42,5 @@
             systemui:framePadding="0dp"
             systemui:frameWidth="0dp">
         </com.android.systemui.statusbar.phone.UserAvatarView>
-    </FrameLayout>
+    </com.android.systemui.animation.LaunchableFrameLayout>
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
new file mode 100644
index 0000000..89d88fe
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:minHeight="52dp"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:background="@drawable/keyguard_settings_popup_menu_background"
+    android:paddingStart="16dp"
+    android:paddingEnd="24dp"
+    android:paddingVertical="16dp">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="20dp"
+        android:layout_height="20dp"
+        android:layout_marginEnd="16dp"
+        android:tint="?android:attr/textColorPrimary"
+        android:importantForAccessibility="no"
+        tools:ignore="UseAppTint" />
+
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="14sp"
+        android:maxLines="1"
+        android:ellipsize="end" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index b76de5a..e182a6a 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -24,6 +24,7 @@
     android:orientation="vertical">
 
     <LinearLayout
+        android:id="@+id/media_metadata_section"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="start|center_vertical"
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
new file mode 100644
index 0000000..c54c4e4
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_recommendation_view.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<!-- Layout for media recommendation item inside QSPanel carousel -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Album cover -->
+    <ImageView
+        android:id="@+id/media_cover"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:translationZ="0dp"
+        android:scaleType="centerCrop"
+        android:adjustViewBounds="true"
+        android:clipToOutline="true"
+        android:background="@drawable/bg_smartspace_media_item"/>
+
+    <!-- App icon -->
+    <com.android.internal.widget.CachingIconView
+        android:id="@+id/media_rec_app_icon"
+        android:layout_width="@dimen/qs_media_rec_icon_top_margin"
+        android:layout_height="@dimen/qs_media_rec_icon_top_margin"
+        android:layout_marginStart="@dimen/qs_media_info_spacing"
+        android:layout_marginTop="@dimen/qs_media_info_spacing"/>
+
+    <!-- Artist name -->
+    <TextView
+        android:id="@+id/media_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/qs_media_info_spacing"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        android:layout_marginBottom="@dimen/qs_media_rec_album_title_bottom_margin"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:singleLine="true"
+        android:textSize="12sp"
+        android:gravity="top"
+        android:layout_gravity="bottom"
+        android:importantForAccessibility="no"/>
+
+    <!-- Album name -->
+    <TextView
+        android:id="@+id/media_subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_rec_album_subtitle_height"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        android:layout_marginStart="@dimen/qs_media_info_spacing"
+        android:layout_marginBottom="@dimen/qs_media_info_spacing"
+        android:fontFamily="@*android:string/config_headlineFontFamily"
+        android:singleLine="true"
+        android:textSize="11sp"
+        android:gravity="center_vertical"
+        android:layout_gravity="bottom"
+        android:importantForAccessibility="no"/>
+
+    <!-- Seek Bar -->
+    <SeekBar
+        android:id="@+id/media_progress_bar"
+        android:layout_width="match_parent"
+        android:layout_height="12dp"
+        android:layout_gravity="bottom"
+        android:maxHeight="@dimen/qs_media_enabled_seekbar_height"
+        android:thumb="@android:color/transparent"
+        android:splitTrack="false"
+        android:clickable="false"
+        android:progressTint="?android:attr/textColorPrimary"
+        android:progressBackgroundTint="?android:attr/textColorTertiary"
+        android:paddingTop="5dp"
+        android:paddingBottom="5dp"
+        android:paddingStart="0dp"
+        android:paddingEnd="0dp"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        android:layout_marginStart="@dimen/qs_media_info_spacing"
+        android:layout_marginBottom="@dimen/qs_media_info_spacing"/>
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendations.xml b/packages/SystemUI/res/layout/media_recommendations.xml
new file mode 100644
index 0000000..65fc19c
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_recommendations.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<!-- Layout for media recommendations inside QSPanel carousel -->
+<com.android.systemui.util.animation.TransitionLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/media_recommendations_updated"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:forceHasOverlappingRendering="false"
+    android:background="@drawable/qs_media_background"
+    android:theme="@style/MediaPlayer">
+
+    <!-- This view just ensures the full media player is a certain height. -->
+    <View
+        android:id="@+id/sizing_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_expanded" />
+
+    <TextView
+        android:id="@+id/media_rec_title"
+        style="@style/MediaPlayer.Recommendation.Header"
+        android:text="@string/controls_media_smartspace_rec_header"/>
+
+    <FrameLayout
+        android:id="@+id/media_cover1_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        >
+
+        <include
+            layout="@layout/media_recommendation_view"/>
+
+    </FrameLayout>
+
+
+    <FrameLayout
+        android:id="@+id/media_cover2_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        >
+
+        <include
+            layout="@layout/media_recommendation_view"/>
+
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/media_cover3_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        >
+
+        <include
+            layout="@layout/media_recommendation_view"/>
+
+    </FrameLayout>
+
+    <include
+        layout="@layout/media_long_press_menu" />
+
+</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index abc8337..f2e114b 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -106,7 +106,7 @@
         app:layout_constrainedWidth="true"
         app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
         app:layout_constraintHeight_min="@dimen/min_clickable_item_size">
-        <LinearLayout
+        <com.android.systemui.common.ui.view.LaunchableLinearLayout
             android:id="@+id/media_seamless_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -135,7 +135,7 @@
                 android:textDirection="locale"
                 android:textSize="12sp"
                 android:lineHeight="16sp" />
-        </LinearLayout>
+        </com.android.systemui.common.ui.view.LaunchableLinearLayout>
     </LinearLayout>
 
     <!-- Song name -->
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 21d12c2..4483db8 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -27,6 +27,14 @@
         android:layout_height="wrap_content"
         />
 
+    <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
+        android:id="@+id/icon_glow_ripple"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        />
+
+    <!-- Add a bottom margin to avoid the glow of the icon ripple from being cropped by screen
+     bounds while animating with the icon -->
     <com.android.internal.widget.CachingIconView
         android:id="@+id/app_icon"
         android:background="@drawable/media_ttt_chip_background_receiver"
@@ -34,6 +42,7 @@
         android:layout_height="@dimen/media_ttt_icon_size_receiver"
         android:layout_gravity="center|bottom"
         android:alpha="0.0"
+        android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin"
         />
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml
index c949ba0..18d231c 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml
@@ -23,7 +23,7 @@
     android:layout_gravity="center_vertical|start"
     android:layout_marginStart="5dp"
 >
-    <LinearLayout
+    <com.android.systemui.common.ui.view.LaunchableLinearLayout
         android:id="@+id/ongoing_call_chip_background"
         android:layout_width="wrap_content"
         android:layout_height="@dimen/ongoing_appops_chip_height"
@@ -55,5 +55,5 @@
             android:textColor="?android:attr/colorPrimary"
         />
 
-    </LinearLayout>
+    </com.android.systemui.common.ui.view.LaunchableLinearLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 5aa6080..d1a2cf4 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -25,6 +25,7 @@
     android:focusable="true"
     android:clipChildren="false"
     android:clipToPadding="false"
+    android:paddingStart="8dp"
     >
 
         <LinearLayout
diff --git a/packages/SystemUI/res/layout/screenshot_detection_notice.xml b/packages/SystemUI/res/layout/screenshot_detection_notice.xml
new file mode 100644
index 0000000..fc936c0
--- /dev/null
+++ b/packages/SystemUI/res/layout/screenshot_detection_notice.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/screenshot_detection_notice"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:padding="12dp"
+    android:visibility="gone">
+
+    <TextView
+        android:id="@+id/screenshot_detection_notice_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:lineHeight="24sp"
+        android:textSize="18sp" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 496eb6e..a748e29 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -31,7 +31,7 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="@+id/actions_container"
         app:layout_constraintEnd_toEndOf="@+id/actions_container"
-        app:layout_constraintBottom_toTopOf="@id/screenshot_message_container"/>
+        app:layout_constraintBottom_toTopOf="@id/guideline"/>
     <HorizontalScrollView
         android:id="@+id/actions_container"
         android:layout_width="0dp"
@@ -127,57 +127,30 @@
         app:layout_constraintTop_toTopOf="@id/screenshot_preview"
         android:elevation="7dp"/>
 
-    <androidx.constraintlayout.widget.ConstraintLayout
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_end="0dp" />
+
+    <FrameLayout
         android:id="@+id/screenshot_message_container"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
-        android:layout_marginVertical="4dp"
+        android:layout_marginTop="4dp"
+        android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
         android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
         android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
         android:elevation="4dp"
         android:background="@drawable/action_chip_container_background"
         android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@id/guideline"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent">
-
-        <ImageView
-            android:id="@+id/screenshot_message_icon"
-            android:layout_width="48dp"
-            android:layout_height="48dp"
-            android:paddingEnd="4dp"
-            android:src="@drawable/ic_work_app_badge"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/screenshot_message_content"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"/>
-
-        <TextView
-            android:id="@+id/screenshot_message_content"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_gravity="start"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toEndOf="@id/screenshot_message_icon"
-            app:layout_constraintEnd_toStartOf="@id/message_dismiss_button"/>
-
-        <FrameLayout
-            android:id="@+id/message_dismiss_button"
-            android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
-            android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
-            app:layout_constraintStart_toEndOf="@id/screenshot_message_content"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            android:contentDescription="@string/screenshot_dismiss_work_profile">
-            <ImageView
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_margin="@dimen/overlay_dismiss_button_margin"
-                android:src="@drawable/overlay_cancel"/>
-        </FrameLayout>
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
+        >
+        <include layout="@layout/screenshot_work_profile_first_run" />
+        <include layout="@layout/screenshot_detection_notice" />
+    </FrameLayout>
 </com.android.systemui.screenshot.DraggableConstraintLayout>
diff --git a/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
new file mode 100644
index 0000000..392d845
--- /dev/null
+++ b/packages/SystemUI/res/layout/screenshot_work_profile_first_run.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/work_profile_first_run"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:paddingStart="16dp"
+    android:paddingEnd="4dp"
+    android:paddingVertical="16dp"
+    android:visibility="gone">
+    <ImageView
+        android:id="@+id/screenshot_message_icon"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_marginEnd="12dp"
+        android:layout_gravity="center_vertical"
+        android:src="@drawable/ic_work_app_badge"/>
+
+    <TextView
+        android:id="@+id/screenshot_message_content"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_gravity="start|center_vertical"
+        android:textSize="18sp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:lineHeight="24sp"
+        />
+
+    <FrameLayout
+        android:id="@+id/message_dismiss_button"
+        android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
+        android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
+        android:contentDescription="@string/screenshot_dismiss_work_profile">
+        <ImageView
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_gravity="center"
+            android:src="@drawable/overlay_cancel"/>
+    </FrameLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 3b71dc3..64aa629 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -149,11 +149,4 @@
         </FrameLayout>
     </LinearLayout>
 
-    <ViewStub
-        android:id="@+id/emergency_cryptkeeper_text"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout="@layout/emergency_cryptkeeper_text"
-    />
-
 </com.android.systemui.statusbar.phone.PhoneStatusBarView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 159323a..3c860a9 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -26,6 +26,11 @@
     android:layout_height="match_parent"
     android:background="@android:color/transparent">
 
+    <com.android.systemui.common.ui.view.LongPressHandlingView
+        android:id="@+id/keyguard_long_press"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <ViewStub
         android:id="@+id/keyguard_qs_user_switch_stub"
         android:layout="@layout/keyguard_qs_user_switch"
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index bbb8df1c..db94c92 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -26,6 +26,17 @@
         android:id="@+id/content"
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
+        <TextView
+            android:id="@+id/unlock_prompt_footer"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dp"
+            android:layout_gravity="center_horizontal"
+            android:gravity="center"
+            android:drawablePadding="8dp"
+            android:visibility="gone"
+            android:textAppearance="?android:attr/textAppearanceButton"
+            android:text="@string/unlock_to_see_notif_text"/>
         <com.android.systemui.statusbar.notification.row.FooterViewButton
             style="@style/TextAppearance.NotificationSectionHeaderButton"
             android:id="@+id/manage_text"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index c0509a9..fb4a3cb 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ondergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linkergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Regtergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Werkskermskote word in die <xliff:g id="APP">%1$s</xliff:g>-app gestoor"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Lêers"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Skermopnemer"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Verwerk tans skermopname"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Deurlopende kennisgewing vir \'n skermopnamesessie"</string>
@@ -373,11 +375,11 @@
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Begin opneem of uitsaai met <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Laat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toe om te deel of op te neem?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Hele skerm"</string>
-    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"’n Enkele program"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"’n Enkele app"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Wanneer jy deel, opneem of uitsaai, het <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Wanneer jy ’n program deel, opneem of uitsaai, het <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot enigiets wat in daardie program sigbaar is of daarin gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Gaan voort"</string>
-    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deel of neem ’n program op"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deel of neem ’n app op"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Laat hierdie app toe om te deel of op te neem?"</string>
     <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Wanneer jy deel, opneem of uitsaai, het hierdie app toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
     <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Wanneer jy ’n app deel, opneem of uitsaai, het hierdie app toegang tot enigiets wat in daardie program sigbaar is of daarin gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Outomaties"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Geen klank of vibrasie nie"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Geen klank of vibrasie nie en verskyn laer in gespreksafdeling"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan lui of vibreer op grond van fooninstellings"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan lui of vibreer op grond van fooninstellings. Gesprekke van <xliff:g id="APP_NAME">%1$s</xliff:g> af verskyn by verstek in \'n borrel."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan lui of vibreer op grond van toestelinstellings"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan lui of vibreer op grond van toestelinstellings. Gesprekke van <xliff:g id="APP_NAME">%1$s</xliff:g> af verskyn by verstek in ’n borrel."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Laat die stelsel bepaal of hierdie kennisgewing \'n klank moet maak of vibreer"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Bevorder na Verstek"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Gedegradeer na Stil"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Kies program om kontroles by te voeg"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrole bygevoeg.}other{# kontroles bygevoeg.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Verwyder"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Voeg <xliff:g id="APPNAME">%s</xliff:g> by?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Wanneer jy <xliff:g id="APPNAME">%s</xliff:g> byvoeg, kan dit kontroles en inhoud by hierdie paneel voeg. Jy kan in sommige apps kies watter kontroles hier verskyn."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"As gunsteling gemerk"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"As gunsteling gemerk; posisie <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"As gunsteling ontmerk"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Ontdoen"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Beweeg nader om op <xliff:g id="DEVICENAME">%1$s</xliff:g> te speel"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Beweeg nader aan <xliff:g id="DEVICENAME">%1$s</xliff:g> om hier te speel"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Beweeg nader aan <xliff:g id="DEVICENAME">%1$s</xliff:g> om hier te speel"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Speel tans op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Iets is fout. Probeer weer."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Laai tans"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Onaktief, gaan program na"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nie gekry nie"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrole is nie beskikbaar nie"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Fout, probeer weer"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Voeg kontroles by"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Wysig kontroles"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Voeg app by"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Voeg uitvoere by"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 toestel gekies"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Luidsprekers en skerms"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Voorgestelde toestelle"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Hoe uitsaai werk"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Saai uit"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Mense in jou omtrek met versoenbare Bluetooth-toestelle kan na die media luister wat jy uitsaai"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Hierdie skerm sal afskakel"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Voubare toestel word ontvou"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Voubare toestel word omgekeer"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batterykrag oor"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Koppel jou stilus aan ’n laaier"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stilus se battery is amper pap"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Kan nie van hierdie profiel af bel nie"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Jou werkbeleid laat jou toe om slegs van die werkprofiel af foonoproepe te maak"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Skakel oor na werkprofiel"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Maak toe"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index d0c5c90..bf00ec3 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"የታች ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"የግራ ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"የቀኝ ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"የሥራ ቅጽበታዊ ገጽ እይታዎች በ<xliff:g id="APP">%1$s</xliff:g> መተግበሪያ ውስጥ ይቀመጣሉ"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ፋይሎች"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"የማያ መቅጃ"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"የማያ ገጽ ቀረጻን በማሰናዳት ላይ"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ለአንድ የማያ ገጽ ቀረጻ ክፍለ-ጊዜ በመካሄድ ያለ ማሳወቂያ"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ራስ-ሰር"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"ምንም ድምፅ ወይም ንዝረት የለም"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ምንም ድምፅ ወይም ንዝረት የለም እና በውይይት ክፍል ላይ አይታይም"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"በእርስዎ የስልክ ቅንብሮች የሚወሰን ሆኖ ሊደውል ወይም ሊነዝር ይችላል። የ<xliff:g id="APP_NAME">%1$s</xliff:g> አረፋ ውይይቶች በነባሪነት።"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"በመሣሪያ ቅንብሮች መሰረት ሊጮህ ወይም ሊነዝር ይችላል"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"በስልክ ቅንብሮች መሰረት ሊጮህ ወይም ሊነዝር ይችላል። የ<xliff:g id="APP_NAME">%1$s</xliff:g> ውይይቶች በነባሪነት አረፋ ይሆናሉ።"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ይህ ማሳወቂያ ድምፅ ወይም ንዝረት መደረግ ካለበት ስርዓቱ እንዲወሰን ያድርጉት"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;ሁኔታ:&lt;/b&gt; ለነባሪ ከፍ ተዋውቋል።"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;ሁኔታ:&lt;/b&gt; ወደ ዝምታ ዝቅ ተደርጓል"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"መቆጣጠሪያዎችን ለማከል መተግበሪያ ይምረጡ"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ቁጥጥር ታክሏል።}one{# ቁጥጥር ታክሏል።}other{# ቁጥጥሮች ታክለዋል።}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"ተወግዷል"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"ተወዳጅ የተደረገ"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ተወዳጅ ተደርጓል፣ አቋም <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ተወዳጅ አልተደረገም"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ከ<xliff:g id="APP_LABEL">%2$s</xliff:g> ያጫውቱ"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"ቀልብስ"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ ለማጫወት ጠጋ ያድርጉ"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"እዚህ ለመጫወት ወደ <xliff:g id="DEVICENAME">%1$s</xliff:g> ቀረብ ይበሉ"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"እዚህ ለማጫወት ወደ <xliff:g id="DEVICENAME">%1$s</xliff:g> ጠጋ ይበሉ"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ በማጫወት ላይ"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"የሆነ ችግር ተፈጥሯል። እንደገና ይሞክሩ።"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"በመጫን ላይ"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ጡባዊ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"አልተገኘም"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"መቆጣጠሪያ አይገኝም"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"ስህተት፣ እንደገና ይሞክሩ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"መቆጣጠሪያዎችን አክል"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"መቆጣጠሪያዎችን ያርትዑ"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ውጽዓቶችን ያክሉ"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ቡድን"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 መሣሪያ ተመርጧል"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"የድምጽ መጠን"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ድምጽ ማውጫዎች እና ማሳያዎች"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"የተጠቆሙ መሣሪያዎች"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ማሰራጨት እንዴት እንደሚሠራ"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ስርጭት"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ተኳሃኝ የብሉቱዝ መሣሪያዎች ያላቸው በአቅራቢያዎ ያሉ ሰዎች እርስዎ እያሰራጩት ያሉትን ሚዲያ ማዳመጥ ይችላሉ"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ይህ ማያ ገጽ ይጠፋል"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"መታጠፍ የሚችል መሣሪያ እየተዘረጋ ነው"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"መታጠፍ የሚችል መሣሪያ እየተገለበጠ ነው"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ባትሪ ይቀራል"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ብሮስፌዎን ከኃይል መሙያ ጋር ያገናኙ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"የብሮስፌ ባትሪ ዝቅተኛ ነው"</string>
+    <string name="video_camera" msgid="7654002575156149298">"የቪድዮ ካሜራ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"ከዚህ መገለጫ መደወል አይቻልም"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"የሥራ መመሪያዎ እርስዎ ከሥራ መገለጫው ብቻ ጥሪ እንዲያደርጉ ይፈቅድልዎታል"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"ወደ የሥራ መገለጫ ቀይር"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"ዝጋ"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index ea5bce1..804e565 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"الحد السفلى <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"الحد الأيسر <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"الحد الأيمن <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"يتم حفظ لقطات الشاشة الخاصة بالعمل في تطبيق \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"الملفات"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"مسجّل الشاشة"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"جارٍ معالجة تسجيل الشاشة"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"إشعار مستمر لجلسة تسجيل شاشة"</string>
@@ -371,7 +373,7 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ستتمكن الخدمة التي تقدّم هذه الوظيفة من الوصول إلى كل المعلومات المرئية لك على الشاشة أو التي يتم تشغيلها على جهازك أثناء التسجيل أو البث. ويشمل ذلك معلومات مثل كلمات المرور وتفاصيل الدفع والصور والرسائل والمقاطع الصوتية التي تشغِّلها."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"هل تريد بدء التسجيل أو البث؟"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"هل تريد بدء التسجيل أو الإرسال باستخدام <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>؟"</string>
-    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"هل تريد السماح لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>بالمشاركة أو التسجيل؟"</string>
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"هل تريد السماح لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> بالمشاركة أو التسجيل؟"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"الشاشة بالكامل"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"تطبيق واحد"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"أثناء المشاركة أو التسجيل أو البث، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
@@ -434,7 +436,7 @@
     <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"‏تطبيقات العمل الخاصة بك متّصلة بالإنترنت من خلال <xliff:g id="VPN_APP">%1$s</xliff:g>. يمكن لمشرف تكنولوجيا المعلومات ومزوّد خدمة الشبكة الافتراضية الخاصة (VPN) رؤية أنشطة الشبكة في تطبيقات العمل، بما في ذلك الرسائل الإلكترونية وبيانات التصفُّح."</string>
     <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"‏تطبيقاتك الشخصية متّصلة بالإنترنت من خلال <xliff:g id="VPN_APP">%1$s</xliff:g>. تظهر أنشطة الشبكة، بما في ذلك الرسائل الإلكترونية وبيانات التصفُّح، لمزوّد خدمة الشبكة الافتراضية الخاصة (VPN)."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
-    <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"‏فتح إعدادات الشبكة الافتراضية الخاصة (VPN)"</string>
+    <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"‏فتح إعدادات شبكة VPN"</string>
     <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"يتولّى أحد الوالدين إدارة هذا الجهاز. يمكن للوالدين عرض وإدارة معلوماتك، مثلاً التطبيقات التي تستخدمها وموقعك الجغرافي ووقت النظر إلى الشاشة."</string>
     <string name="legacy_vpn_name" msgid="4174223520162559145">"شبكة افتراضية خاصة"</string>
     <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"‏فتح القفل باستمرار بواسطة TrustAgent"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"تلقائي"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"بدون صوت أو اهتزاز"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"بدون صوت أو اهتزاز وتظهر في أسفل قسم المحادثات"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"يمكن إصدار رنين أو اهتزاز بناءً على إعدادات الهاتف."</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"يمكن إصدار رنين أو اهتزاز بناءً على إعدادات الهاتف. تظهر المحادثات من <xliff:g id="APP_NAME">%1$s</xliff:g> كفقاعات تلقائيًا."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"يمكن إصدار رنين أو اهتزاز بناءً على إعدادات الجهاز"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"يمكن إصدار رنين أو اهتزاز بناءً على إعدادات الجهاز. تظهر المحادثات من \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" كفقاعات تلقائيًا."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"السماح للنظام بتحديد ما إذا يجب اهتزاز الجهاز أو إصدار رنين عند تلقّي هذا الإشعار"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"‏&lt;b&gt;الحالة:&lt;/b&gt; تمت الترقية إلى الإعداد التلقائي"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"‏&lt;b&gt;الحالة:&lt;/b&gt; تم خفض الترتيب إلى الوضع صامت"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"اختيار تطبيق لإضافة عناصر التحكّم"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{تمت إضافة عنصر تحكّم واحد.}zero{تمت إضافة # عنصر تحكّم.}two{تمت إضافة عنصرَي تحكّم.}few{تمت إضافة # عناصر تحكّم.}many{تمت إضافة # عنصر تحكّم.}other{تمت إضافة # عنصر تحكّم.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"تمت الإزالة"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"تمت الإضافة إلى المفضّلة"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"تمت الإضافة إلى المفضّلة، الموضع <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"تمت الإزالة من المفضّلة"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"تراجع"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"عليك الاقتراب لتشغيل الوسائط على <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"يُرجى الاقتراب من <xliff:g id="DEVICENAME">%1$s</xliff:g> لتشغيل الوسائط هنا."</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"للتشغيل هنا، عليك الاقتراب من \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\"."</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"جارٍ تشغيل الوسائط على <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"حدث خطأ. يُرجى إعادة المحاولة."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"جارٍ التحميل"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"جهاز لوحي"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"غير نشط، تحقّق من التطبيق."</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"لم يتم العثور عليه."</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"عنصر التحكّم غير متوفّر"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"حدث خطأ، يُرجى إعادة المحاولة."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"إضافة عناصر تحكّم"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"تعديل عناصر التحكّم"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"إضافة مخرجات"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"مجموعة"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"تم اختيار جهاز واحد."</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"مستوى الصوت"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"مكبّرات الصوت والشاشات"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"الأجهزة المقترَحة"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"كيفية عمل البث"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"البث"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"يمكن للأشخاص القريبين منك الذين لديهم أجهزة متوافقة تتضمّن بلوتوث الاستماع إلى الوسائط التي تبثها."</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* سيتم إطفاء هذه الشاشة."</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"جهاز قابل للطي يجري فتحه"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"جهاز قابل للطي يجري قلبه"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"النسبة المئوية المتبقية من شحن البطارية: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"عليك توصيل قلم الشاشة بشاحن."</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"بطارية قلم الشاشة منخفضة"</string>
+    <string name="video_camera" msgid="7654002575156149298">"كاميرا فيديو"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"لا يمكن الاتصال باستخدام هذا الملف الشخصي."</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"تسمح لك سياسة العمل بإجراء المكالمات الهاتفية من الملف الشخصي للعمل فقط."</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"التبديل إلى الملف الشخصي للعمل"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"إغلاق"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index cbd2bed..9e08a81 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"তলৰ সীমা <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"বাওঁফালৰ সীমা <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"সোঁফালৰ সীমা <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"<xliff:g id="APP">%1$s</xliff:g> এপ্‌টোত কৰ্মস্থানৰ স্ক্ৰীনশ্বটসমূহ ছেভ কৰা হয়"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ফাইল"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"স্ক্ৰীন ৰেকৰ্ডাৰ"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"স্ক্রীন ৰেকৰ্ডিঙৰ প্ৰক্ৰিয়াকৰণ হৈ আছে"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রীন ৰেকৰ্ডিং ছেশ্বন চলি থকা সময়ত পোৱা জাননী"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"স্বয়ংক্ৰিয়"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"কোনো ধ্বনি অথবা কম্পন নাই"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"কোনো ধ্বনি অথবা কম্পন নাই আৰু বাৰ্তালাপ শাখাটোৰ তলৰ অংশত দেখা পোৱা যায়"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ফ’নৰ ছেটিঙৰ ওপৰত নিৰ্ভৰ কৰি ৰিং কৰিব অথবা কম্পন হ’ব পাৰে"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ফ’নৰ ছেটিঙৰ ওপৰত নিৰ্ভৰ কৰি ৰিং কৰিব অথবা কম্পন হ’ব পাৰে। <xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাৰ্তালাপ ডিফ’ল্ট হিচাপে বাবল হয়।"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"ডিভাইচৰ ছেটিঙৰ ওপৰত নিৰ্ভৰ কৰি ৰিং কৰিব অথবা কম্পন হ’ব পাৰে"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ডিভাইচৰ ছেটিঙৰ ওপৰত নিৰ্ভৰ কৰি ৰিং কৰিব অথবা কম্পন হ’ব পাৰে। <xliff:g id="APP_NAME">%1$s</xliff:g>ৰ বাৰ্তালাপ ডিফ’ল্টভাৱে বাবল হিচাপে প্ৰদৰ্শিত হয়।"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"এই জাননীটোৱে ধ্বনি নে কম্পন সৃষ্টি কৰিব সেয়া ছিষ্টেমটোক নিৰ্ধাৰণ কৰিবলৈ দিয়ক"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;স্থিতি:&lt;/b&gt; ডিফ’ল্টলৈ বৃদ্ধি কৰা হৈছে"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;স্থিতি:&lt;/b&gt; নীৰৱলৈ হ্ৰাস কৰা হৈছে"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"নিয়ন্ত্ৰণসমূহ যোগ কৰিবলৈ এপ্‌ বাছনি কৰক"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# টা নিয়ন্ত্ৰণ যোগ দিয়া হৈছে।}one{# টা নিয়ন্ত্ৰণ যোগ দিয়া হৈছে।}other{# টা নিয়ন্ত্ৰণ যোগ দিয়া হৈছে।}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"আঁতৰোৱা হ’ল"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"প্ৰিয় হিচাপে চিহ্নিত কৰা হ’ল"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"প্ৰিয় হিচাপে চিহ্নিত কৰা হ’ল, স্থান <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"অপ্ৰিয় হিচাপে চিহ্নিত কৰা হ’ল"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ত <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"আনডু কৰক"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে’ কৰিবলৈ ওচৰলৈ যাওক"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ইয়াত খেলিবলৈ <xliff:g id="DEVICENAME">%1$s</xliff:g>ৰ আৰু ওচৰলৈ যাওক"</string>
-    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে কৰি থকা হৈছে"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ইয়াত প্লে’ কৰিবলৈ, <xliff:g id="DEVICENAME">%1$s</xliff:g>ৰ ওচৰলৈ যাওক"</string>
+    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে\' কৰি থকা হৈছে"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"কিবা ভুল হ’ল। পুনৰ চেষ্টা কৰক।"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"ল’ড হৈ আছে"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"টেবলেট"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"সক্ৰিয় নহয়, এপ্‌টো পৰীক্ষা কৰক"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"বিচাৰি পোৱা নগ’ল"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"নিয়ন্ত্ৰণটো উপলব্ধ নহয়"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"আসোঁৱাহ হৈছে, আকৌ চেষ্টা কৰক"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"নিয়ন্ত্ৰণসমূহ যোগ দিয়ক"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"নিয়ন্ত্ৰণসমূহ সম্পাদনা কৰক"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"আউটপুটসমূহ যোগ দিয়ক"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"গোট"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"১ টা ডিভাইচ বাছনি কৰা হৈছে"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ভলিউম"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"স্পীকাৰ আৰু ডিছপ্লে’"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"পৰামৰ্শ হিচাপে পোৱা ডিভাইচ"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"সম্প্ৰচাৰ কৰাটোৱে কেনেকৈ কাম কৰে"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"সম্প্ৰচাৰ"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"সমিল ব্লুটুথ ডিভাইচৰ সৈতে আপোনাৰ নিকটৱৰ্তী স্থানত থকা লোকসকলে আপুনি সম্প্ৰচাৰ কৰা মিডিয়াটো শুনিব পাৰে"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ই স্ক্ৰীনখন অফ হ’ব"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"জপাব পৰা ডিভাইচৰ জাপ খুলি থকা হৈছে"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"জপাব পৰা ডিভাইচৰ ওলোটাই থকা হৈছে"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> বেটাৰী বাকী আছে"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"আপোনাৰ ষ্টাইলাছ এটা চাৰ্জাৰৰ সৈতে সংযোগ কৰক"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ষ্টাইলাছৰ বেটাৰী কম আছে"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ভিডিঅ’ কেমেৰা"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"এই প্ৰ’ফাইলৰ পৰা কল কৰিব নোৱাৰি"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"আপোনাৰ কৰ্মস্থানৰ নীতিয়ে আপোনাক কেৱল কৰ্মস্থানৰ প্ৰ’ফাইলৰ পৰা ফ’ন কল কৰিবলৈ দিয়ে"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"কৰ্মস্থানৰ প্ৰ’ফাইললৈ সলনি কৰক"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"বন্ধ কৰক"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index dd4d510..73149e8 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Aşağı sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"İş skrinşotları <xliff:g id="APP">%1$s</xliff:g> tətbiqində saxlanılır"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fayllar"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Ekran Yazıcısı"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekran çəkilişi emal edilir"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekranın video çəkimi ərzində silinməyən bildiriş"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Avtomatik"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Səs və ya vibrasiya yoxdur"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Söhbət siyahısının aşağısında səssiz və vibrasiyasız görünür"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Telefon ayarlarına əsasən zəng çala və ya titrəyə bilər"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Telefon ayarlarına əsasən zəng çala və ya vibrasiya edə bilər. <xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqindən söhbətlərdə defolt olaraq qabarcıq çıxır."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Cihaz ayarlarına əsasən zəng çala və ya vibrasiya edə bilər"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Cihaz ayarlarına əsasən zəng çala və ya vibrasiya edə bilər. <xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqindən söhbətlərdə defolt olaraq qabarcıq çıxır."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Bu bildirişin səs çıxarması və ya vibrasiya etməsi sistem tərəfindən təyin edilsin"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Defolt ayara keçirilib"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Səssiz rejimə keçirilib"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Kontrol əlavə etmək üçün tətbiq seçin"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# nizamlayıcı əlavə edilib.}other{# nizamlayıcı əlavə edilib.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Silinib"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> əlavə edilsin?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> əlavə etdiyiniz zaman, o, bu panelə nizamlayıcılar və məzmun əlavə edə bilər. Bəzi tətbiqlərdə burada hansı nizamlayıcıların göstərilməsini seçə bilərsiniz."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Sevimlilərə əlavə edilib"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Sevimlilərə əlavə edilib, sıra: <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Sevimlilərdən silinib"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%2$s</xliff:g> tətbiqindən oxudun"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Geri qaytarın"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxutmaq üçün yaxınlaşın"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Burada oxutmaq üçün <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaxınlaşın"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Burada oxutmaq üçün <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaxınlaşdırın"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxudulur"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Xəta oldu. Yenə cəhd edin."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Yüklənir"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"planşet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Aktiv deyil, tətbiqi yoxlayın"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Tapılmadı"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Nəzarət əlçatan deyil"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Xəta, yenidən cəhd edin"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Vidcet əlavə edin"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Vidcetlərə düzəliş edin"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Tətbiq əlavə edin"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Nəticələri əlavə edin"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Qrup"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçilib"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Səs"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Dinamiklər &amp; Displeylər"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Təklif olunan Cihazlar"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayım necə işləyir"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Yayım"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Uyğun Bluetooth cihazları olan yaxınlığınızdakı insanlar yayımladığınız medianı dinləyə bilər"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Bu ekran deaktiv ediləcək"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Qatlana bilən cihaz açılır"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Qatlana bilən cihaz fırladılır"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> enerji qalıb"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Qələmi adapterə qoşun"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Qələm enerjisi azdır"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Bu profildən zəng etmək mümkün deyil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"İş siyasətiniz yalnız iş profilindən telefon zəngləri etməyə imkan verir"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"İş profilinə keçin"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Bağlayın"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 6431389..89d72a1 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Donja ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Leva ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desna ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Snimci ekrana za posao se čuvaju u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fajlovi"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Snimač ekrana"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrađujemo video snimka ekrana"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Obaveštenje o sesiji snimanja ekrana je aktivno"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatska"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Bez zvuka i vibriranja"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Bez zvuka i vibriranja i prikazuje se u nastavku odeljka za konverzacije"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Može da zvoni ili vibrira u zavisnosti od podešavanja telefona"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Može da zvoni ili vibrira u zavisnosti od podešavanja telefona. Konverzacije iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> se podrazumevano prikazuju u oblačićima."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Može da zvoni ili vibrira u zavisnosti od podešavanja uređaja"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Može da zvoni ili vibrira u zavisnosti od podešavanja uređaja. Konverzacije iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> podrazumevano se prikazuju u oblačićima."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Neka sistem utvrdi da li ovo obaveštenje treba da emituje zvuk ili da vibrira"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Unapređeno u Podrazumevano"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Degradirano u Nečujno"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju za dodavanje kontrola"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrola je dodata.}one{# kontrola je dodata.}few{# kontrole su dodate.}other{# kontrola je dodato.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Uklonjeno"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Želite li da dodate <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Kada dodate aplikaciju <xliff:g id="APPNAME">%s</xliff:g>, ona može da dodaje kontrole i sadržaj u ovo okno. U nekim aplikacijama možete da izaberete koje će se kontrole ovde prikazivati."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Označeno je kao omiljeno"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Označeno je kao omiljeno, <xliff:g id="NUMBER">%d</xliff:g>. pozicija"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Uklonjeno je iz omiljenih"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Opozovi"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite da biste puštali muziku na: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g> da biste na njemu puštali"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Da biste puštali sadržaj ovde, približite uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Pušta se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Došlo je do greške. Probajte ponovo."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Učitava se"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno. Vidite aplikaciju"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Greška. Probajte ponovo"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Izmeni kontrole"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Dodaj aplikaciju"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajte izlaze"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izabran je 1 uređaj"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Zvuk"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvučnici i ekrani"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predloženi uređaji"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako funkcioniše emitovanje"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Emitovanje"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ljudi u blizini sa kompatibilnim Bluetooth uređajima mogu da slušaju medijski sadržaj koji emitujete"</string>
@@ -1008,12 +1014,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• da je dostupan barem jedan uređaj"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Dodirnite i zadržite prečicu"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Otkaži"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Obrnite"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Obrni"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Otvorite telefon za bolji selfi"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Želite da obrnete na prednji ekran za bolji selfi?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Koristite zadnju kameru da biste snimili širu sliku sa višom rezolucijom."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ovaj ekran će se isključiti"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Uređaj na preklop se otvara"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Uređaj na preklop se obrće"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo je još<xliff:g id="PERCENTAGE">%s</xliff:g> baterije"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Povežite pisaljku sa punjačem"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Nizak nivo baterije pisaljke"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Video kamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ne možete da upućujete pozive sa ovog profila"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Smernice za posao vam omogućavaju da telefonirate samo sa poslovnog profila"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Pređi na poslovni profil"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zatvori"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 7e7262a..5bbe0011 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ніжняя граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Левая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Правая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Працоўныя здымкі экрана захаваны ў праграме \"<xliff:g id="APP">%1$s</xliff:g>\""</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Запіс экрана"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Апрацоўваецца запіс экрана"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Бягучае апавяшчэнне для сеанса запісу экрана"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Аўтаматычна"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без гуку ці вібрацыі"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Паказваецца без гуку ці вібрацыі ў раздзеле размоў"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"У залежнасці ад налад тэлефона магчымы званок або вібрацыя"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"У залежнасці ад налад тэлефона магчымы званок або вібрацыя. Размовы ў праграме \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" стандартна паяўляюцца ў выглядзе ўсплывальных апавяшчэнняў."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"У залежнасці ад налад прылады магчымы званок або вібрацыя"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"У залежнасці ад налад прылады магчымы званок або вібрацыя. Размовы ў праграме \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" стандартна паяўляюцца ў выглядзе ўсплывальных апавяшчэнняў."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Сістэма сама будзе вызначаць, ці трэба для гэтага апавяшчэння ўключаць гук або вібрацыю"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Стан:&lt;/b&gt; Пазначана як стандартнае"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Стан:&lt;/b&gt; Пераведзена ў рэжым \"Без гуку\""</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Выберыце праграму для дадавання элементаў кіравання"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Дададзены # элемент кіравання.}one{Дададзена # элемента кіравання.}few{Дададзена # элементы кіравання.}many{Дададзена # элементаў кіравання.}other{Дададзена # элемента кіравання.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Выдалена"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Дададзена ў абранае"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Дададзена ў абранае, пазіцыя <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Выдалена з абранага"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" з дапамогай праграмы \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Адрабіць"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Каб прайграць мультымедыя на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", наблізьцеся да яе"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Падыдзіце бліжэй да прылады \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", каб прайграць на гэтай"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Каб прайграць тут, падыдзіце бліжэй да прылады \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Прайграецца на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Нешта пайшло не так. Паўтарыце спробу."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Ідзе загрузка"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшэт"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактыўна, праверце праграму"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Не знойдзена"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Кіраванне недаступнае"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Памылка, паўтарыце спробу"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Дадаць элементы кіравання"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Змяніць элементы кіравання"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Дадайце прылады вываду"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрана 1 прылада"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Гучнасць"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Дынамікі і дысплэі"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Прылады, якія падтрымліваюцца"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як адбываецца трансляцыя"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Трансляцыя"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Людзі паблізу, у якіх ёсць прылады з Bluetooth, змогуць праслухваць мультымедыйнае змесціва, якое вы трансліруеце"</string>
@@ -1010,10 +1019,19 @@
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Скасаваць"</string>
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Пераключыць"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Каб атрымаць лепшае сэлфі, раскрыйце тэлефон"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Пераключыць на пярэднюю камеру для лепшага сэлфі?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Пераключыць на пярэдні дысплэй для лепшага сэлфі?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Каб зрабіць шырэйшае фота з больш высокай раздзяляльнасцю, скарыстайце заднюю камеру."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Гэты экран будзе выключаны"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складная прылада ў раскладзеным выглядзе"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перавернутая складная прылада"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Засталося зараду: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Падключыце пяро да зараднай прылады"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Нізкі ўзровень зараду пяра"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Відэакамера"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Не ўдалося зрабіць выклік з гэтага профілю"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Згодна з палітыкай вашай арганізацыі, рабіць тэлефонныя выклікі дазволена толькі з працоўнага профілю"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Пераключыцца на працоўны профіль"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Закрыць"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 26a154f..72c6b9d 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Долна граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лява граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Дясна граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Екранните снимки, направени в служебния потребителски профил, се запазват в приложението „<xliff:g id="APP">%1$s</xliff:g>“"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Запис на екрана"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Записът на екрана се обработва"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущо известие за сесия за записване на екрана"</string>
@@ -375,12 +377,12 @@
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Цял екран"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Едно приложение"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когато споделяте, записвате или предавате приложение, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Напред"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделяне или записване на приложение"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Да се разреши ли на това приложение да споделя или записва?"</string>
     <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Когато споделяте, записвате или предавате, това приложение има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
-    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Когато споделяте, записвате или предавате, това приложение има достъп до всичко, което се показва или възпроизвежда в него, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Когато споделяте, записвате или предавате приложение, това приложение има достъп до всичко, което се показва или възпроизвежда в приложението, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано от системния ви администратор"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Заснемането на екрана е деактивирано от правило за устройството"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматично"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звук или вибриране"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звук или вибриране и се показва по-долу в секцията с разговори"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Може да звъни или да вибрира въз основа на настройките за телефона"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Може да звъни или да вибрира според настройките за телефона. Разговорите от <xliff:g id="APP_NAME">%1$s</xliff:g> се показват като балончета по подразбиране."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Може да звъни или да вибрира въз основа на настройките на устройството"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Може да звъни или да вибрира въз основа на настройките на устройството. Разговорите от <xliff:g id="APP_NAME">%1$s</xliff:g> се показват като балончета по подразбиране."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Нека системата да определя дали дадено известие да се придружава от звук, или вибриране"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Състояние:&lt;/b&gt; Повишено до основно"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Състояние:&lt;/b&gt; Понижено до беззвучно"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Изберете приложение, за да добавите контроли"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Добавена е # контрола.}other{Добавени са # контроли.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Премахнато"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Означено като любимо"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Означено като любимо – позиция <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Не е означено като любимо"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> от <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Отмяна"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Преместете се по-близо, за да се възпроизведе на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Приближете се до <xliff:g id="DEVICENAME">%1$s</xliff:g> за възпроизвеждане на това устройство"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"За възпроизвеждане тук се приближете до <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Възпроизвежда се на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Нещо се обърка. Опитайте отново."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Зарежда се"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, проверете прилож."</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Не е намерено"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Контролата не е налице"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Грешка. Опитайте отново"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Добавяне на контроли"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Редактиране на контролите"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Добавяне на изходящи устройства"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 избрано устройство"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Сила на звука"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Високоговорители и екрани"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Предложени устройства"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Как работи предаването"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Предаване"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Хората в близост със съвместими устройства с Bluetooth могат да слушат мултимедията, която предавате"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Този екран ще се изключи"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Разгъване на сгъваемо устройство"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Обръщане на сгъваемо устройство"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Оставаща батерия: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Свържете писалката към зарядно устройство"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Батерията на писалката е изтощена"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Видеокамера"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Не може да се извърши обаждане от този потребителски профил"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Служебните правила ви дават възможност да извършвате телефонни обаждания само от служебния потребителски профил"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Превключване към служебния потребителски профил"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Затваряне"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index b6069a4..598db2c 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"নিচের প্রান্ত থেকে <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"বাঁ প্রান্ত থেকে <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ডান প্রান্ত থেকে <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"অফিসের স্ক্রিনশট <xliff:g id="APP">%1$s</xliff:g> অ্যাপে সেভ করা হয়"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ফাইল"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"স্ক্রিন রেকর্ডার"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"স্ক্রিন রেকর্ডিং প্রসেস হচ্ছে"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রিন রেকর্ডিং সেশন চলার বিজ্ঞপ্তি"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"অটোমেটিক"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"আওয়াজ করবে না বা ভাইব্রেট হবে না"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"আওয়াজ করবে না বা ভাইব্রেট হবে না এবং কথোপকথন বিভাগের নিচের দিকে দেখা যাবে"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ফোনের সেটিংস অনুযায়ী ফোন রিং বা ভাইব্রেট হতে পারে"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ফোনের সেটিংস অনুযায়ী ফোন রিং বা ভাইব্রেট হতে পারে। <xliff:g id="APP_NAME">%1$s</xliff:g>-এর কথোপকথন সাধারণত বাবলের মতো দেখাবে।"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"ডিভাইসের সেটিংস অনুযায়ী রিং বা ভাইব্রেট হতে পারে"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ডিভাইসের সেটিংস অনুযায়ী রিং বা ভাইব্রেট হতে পারে। <xliff:g id="APP_NAME">%1$s</xliff:g>-এর কথোপকথন সাধারণত বাবলের মতো দেখাবে।"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"এই বিজ্ঞপ্তি এলে ডিভাইস আওয়াজ করবে না ভাইব্রেট করবে তা সিস্টেমকে সেট করতে দিন"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;স্ট্যাটাস:&lt;/b&gt; লেভেল বাড়িয়ে ডিফল্ট করা হয়েছে"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;স্ট্যাটাস:&lt;/b&gt; লেভেল কমিয়ে সাইলেন্ করা হয়েছে"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"কন্ট্রোল যোগ করতে অ্যাপ বেছে নিন"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{#টি কন্ট্রোল যোগ করা হয়েছে।}one{#টি কন্ট্রোল যোগ করা হয়েছে।}other{#টি কন্ট্রোল যোগ করা হয়েছে।}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"সরানো হয়েছে"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"পছন্দসই হিসেবে চিহ্নিত করেছেন"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"পছন্দসই হিসেবে চিহ্নিত করেছেন, অবস্থান <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"পছন্দসই থেকে সরিয়ে দিয়েছেন"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%2$s</xliff:g> অ্যাপে চালান"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"আগের অবস্থায় ফিরুন"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ চালাতে আরও কাছে আনুন"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"এখান থেকে চালাতে <xliff:g id="DEVICENAME">%1$s</xliff:g>-এর কাছে নিয়ে যান"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"এখানে চালানোর জন্য আপনার ডিভাইস <xliff:g id="DEVICENAME">%1$s</xliff:g>-এর কাছে নিয়ে যান"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ ভিডিও চালানো হচ্ছে"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"কোনও সমস্যা হয়েছে। আবার চেষ্টা করুন।"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"লোড করা হচ্ছে"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ট্যাবলেট"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"বন্ধ আছে, অ্যাপ চেক করুন"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"খুঁজে পাওয়া যায়নি"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"কন্ট্রোল উপলভ্য নেই"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"সমস্যা হয়েছে, আবার চেষ্টা করুন"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"কন্ট্রোল যোগ করুন"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"কন্ট্রোল এডিট করুন"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"আউটপুট যোগ করুন"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"গ্রুপ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"১টি ডিভাইস বেছে নেওয়া হয়েছে"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ভলিউম"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"স্পিকার &amp; ডিসপ্লে"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"সাজেস্ট করা ডিভাইস"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ব্রডকাস্ট কীভাবে কাজ করে"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"সম্প্রচার করুন"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"আশপাশে লোকজন যাদের মানানসই ব্লুটুথ ডিভাইস আছে, তারা আপনার ব্রডকাস্ট করা মিডিয়া শুনতে পারবেন"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ এই স্ক্রিন বন্ধ হয়ে যাবে"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ফোল্ড করা যায় এমন ডিভাইস খোলা হচ্ছে"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ফোল্ড করা যায় এমন ডিভাইস উল্টানো হচ্ছে"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ব্যাটারির চার্জ বাকি আছে"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"কোনও চার্জারের সাথে আপনার স্টাইলাস কানেক্ট করুন"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"স্টাইলাস ব্যাটারিতে চার্জ কম আছে"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ভিডিও ক্যামেরা"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"এই প্রোফাইল থেকে কল করা যাচ্ছে না"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"কাজ সংক্রান্ত নীতি, আপনাকে শুধুমাত্র অফিস প্রোফাইল থেকে কল করার অনুমতি দেয়"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"অফিস প্রোফাইলে পাল্টে নিন"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"বন্ধ করুন"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 67efa75..88ce4db 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Donja granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Lijeva granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desna granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Poslovni snimci ekrana se pohranjuju u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fajlovi"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Snimač ekrana"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrađivanje snimka ekrana"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Obavještenje za sesiju snimanja ekrana je u toku"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatski"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Bez zvuka ili vibracije"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Bez zvuka ili vibracije i pojavljuje se pri dnu odjeljka razgovora"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Može zvoniti ili vibrirati na osnovu postavki telefona"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Može zvoniti ili vibrirati na osnovu postavki telefona. Razgovori iz oblačića u aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> kao zadana opcija."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Može zvoniti ili vibrirati na osnovu postavki uređaja"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Može zvoniti ili vibrirati na osnovu postavki uređaja. Razgovori iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> prikazuju se u oblačićima prema zadanim postavkama."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Neka sistem odluči treba li se ovo obavještenje oglasiti zvukom ili vibracijom"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; je unaprijeđen u Zadano"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; je unazađen u Nečujno"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju da dodate kontrole"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Dodana je # kontrola.}one{Dodana je # kontrola.}few{Dodane su # kontrole.}other{Dodano je # kontrola.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Uklonjeno"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Dodati aplikaciju <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Kada dodate aplikaciju <xliff:g id="APPNAME">%s</xliff:g>, ona može dodavati kontrole i sadržaj na ovu ploču. U nekim aplikacijama možete odabrati koje kontrole se prikazuju ovdje."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano u omiljeno"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano u omiljeno, pozicija <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Uklonjeno iz omiljenog"</string>
@@ -849,12 +853,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite se da reproducirate na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g> da na njemu reproducirate"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite da reproducirate na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Da reproducirate ovdje, približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Nešto nije uredu. Pokušajte ponovo."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Učitavanje"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, vidite aplikaciju"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Greška, pokušajte ponovo"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrole"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Dodaj aplikaciju"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajte izlaze"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je 1 uređaj"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Jačina zvuka"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvučnici i ekrani"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predloženi uređaji"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako funkcionira emitiranje"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Emitirajte"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osobe u vašoj blizini s kompatibilnim Bluetooth uređajima mogu slušati medijske sadržaje koje emitirate"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ekran će se isključiti"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Sklopivi uređaj se rasklapa"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Sklopivi uređaj se obrće"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo baterije: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Priključite pisaljku na punjač"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Baterija pisaljke je slaba"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Video kamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nije moguće pozvati s ovog profila"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Radna pravila vam dozvoljavaju upućivanje telefonskih poziva samo s radnog profila"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Pređite na radni profil"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zatvori"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 2f48676..953cc35 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Marge inferior <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marge esquerre <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marge dret <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Les captures de pantalla de treball es desen a l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxers"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Gravació de pantalla"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processant gravació de pantalla"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificació en curs d\'una sessió de gravació de la pantalla"</string>
@@ -98,7 +100,7 @@
     <string name="screenrecord_description" msgid="1123231719680353736">"Durant la gravació, el sistema Android pot capturar qualsevol informació sensible que es mostri a la pantalla o que es reprodueixi al dispositiu. Això inclou contrasenyes, informació de pagament, fotos, missatges i àudio."</string>
     <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Grava la pantalla completa"</string>
     <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Grava una sola aplicació"</string>
-    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mentre graves, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Mentre graves contingut, Android pot accedir a tot el que es veu a la pantalla o que es reprodueix al dispositiu. Per això cal que vagis amb compte amb les contrasenyes, les dades de pagament, els missatges o qualsevol altra informació sensible."</string>
     <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Mentre graves una aplicació, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
     <string name="screenrecord_start_recording" msgid="348286842544768740">"Inicia la gravació"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Grava l\'àudio"</string>
@@ -371,11 +373,11 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"El servei que ofereix aquesta funció tindrà accés a tota la informació visible a la teva pantalla o que es reprodueix al dispositiu mentre graves o emets contingut, com ara contrasenyes, detalls dels pagaments, fotos, missatges i àudio que reprodueixis."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vols començar a gravar o emetre contingut?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vols començar a gravar o emetre contingut amb <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vols permetre que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparteixi o gravi?"</string>
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Vols permetre que <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> comparteixi o gravi contingut?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Tota la pantalla"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola aplicació"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Mentre comparteixes, graves o emets contingut, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> pot accedir a tot el que es veu a la pantalla o que es reprodueix a l\'aplicació. Per això cal que vagis amb compte amb les contrasenyes, les dades de pagament, els missatges o qualsevol altra informació sensible."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Comparteix o grava una aplicació"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vols permetre que aquesta aplicació comparteixi o gravi contingut?"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automàtic"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sense so ni vibració"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sense so ni vibració i es mostra més avall a la secció de converses"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pot sonar o vibrar en funció de la configuració del telèfon"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pot sonar o vibrar en funció de la configuració del telèfon. Les converses de l\'aplicació <xliff:g id="APP_NAME">%1$s</xliff:g> es mostren com a bombolles de manera predeterminada."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Pot sonar o vibrar en funció de la configuració del dispositiu"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Pot sonar o vibrar en funció de la configuració del dispositiu. Les converses de l\'aplicació <xliff:g id="APP_NAME">%1$s</xliff:g> es mostren com a bombolles de manera predeterminada."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Fes que el sistema determini si aquesta notificació ha d\'emetre un so o una vibració"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Estat&lt;/b&gt;: s\'ha augmentat a Predeterminat"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Estat&lt;/b&gt;: s\'ha disminuït a Silenci"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Selecciona l\'aplicació per afegir controls"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{S\'ha afegit # control.}many{# controls added.}other{S\'han afegit # controls.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Suprimit"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Afegit als preferits"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Afegit als preferits, posició <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Suprimit dels preferits"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> des de l\'aplicació <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Desfés"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Mou més a prop per reproduir a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Acosta\'t a <xliff:g id="DEVICENAME">%1$s</xliff:g> per reproduir el contingut aquí"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Per reproduir contingut aquí, apropa\'l a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"S\'està reproduint a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"S\'ha produït un error. Torna-ho a provar."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"S\'està carregant"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tauleta"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactiu; comprova l\'aplicació"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"No s\'ha trobat"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"El control no està disponible"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Error; torna-ho a provar"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Afegeix controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edita els controls"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Afegeix sortides"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositiu seleccionat"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volum"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altaveus i pantalles"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositius suggerits"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Com funciona l\'emissió"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Emet"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les persones properes amb dispositius Bluetooth compatibles poden escoltar el contingut multimèdia que emets"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Aquesta pantalla s\'apagarà"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositiu plegable desplegant-se"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositiu plegable girant"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Queda un <xliff:g id="PERCENTAGE">%s</xliff:g> de bateria"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connecta el llapis òptic a un carregador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria del llapis òptic baixa"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Càmera de vídeo"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"No es pot trucar des d\'aquest perfil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"La teva política de treball et permet fer trucades només des del perfil de treball"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Canvia al perfil de treball"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Tanca"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index feeaa32..7c70cd86 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Dolní okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Levý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Pravý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Pracovní snímky obrazovky se ukládají do aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Soubory"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Rekordér obrazovky"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Záznam obrazovky se zpracovává"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Trvalé oznámení o relaci nahrávání"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automaticky"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Žádný zvuk ani vibrace"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Žádný zvuk ani vibrace a zobrazuje se níže v sekci konverzací"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Vyzvání nebo vibruje podle nastavení telefonu"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Vyzvání nebo vibruje podle nastavení telefonu. Konverzace z aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> mají ve výchozím nastavení podobu bublin."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Vyzvání nebo vibruje podle nastavení zařízení"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Vyzvání nebo vibruje podle nastavení zařízení. Konverzace z aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> ve výchozím nastavení bublají."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Nechat systém rozhodnout, zda má toto oznámení vydat zvuk či zavibrovat"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Stav:&lt;/b&gt; priorita zvýšena na Výchozí"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Stav:&lt;/b&gt; priorita snížena na Tiché"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikaci, pro kterou chcete přidat ovládací prvky"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Byl přidán # ovládací prvek.}few{Byly přidány # ovládací prvky.}many{Bylo přidáno # ovládacího prvku.}other{Bylo přidáno # ovládacích prvků.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Odstraněno"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Přidáno do oblíbených"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Přidáno do oblíbených na pozici <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Odebráno z oblíbených"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Vrátit zpět"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Pokud chcete přehrávat na zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>, přibližte se k němu"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Pokud zde chcete přehrávat média, přibližte se k zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pokud obsah chcete přehrát na tomto zařízení, přesuňte ho blíže k zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Přehrávání v zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Došlo k chybě. Zkuste to znovu."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Načítání"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivní, zkontrolujte aplikaci"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nenalezeno"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládání není k dispozici"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Chyba, zkuste to znovu"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Přidat ovládací prvky"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Upravit ovládací prvky"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Přidání výstupů"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Je vybráno 1 zařízení"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hlasitost"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Reproduktory a displeje"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Navrhovaná zařízení"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jak vysílání funguje"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Vysílání"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Lidé ve vašem okolí s kompatibilními zařízeními Bluetooth mohou poslouchat média, která vysíláte"</string>
@@ -1010,10 +1019,19 @@
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Zrušit"</string>
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Otočit"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Rozložte telefon, selfie bude lepší"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Otočit na přední fotoaparát pro lepší selfie?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Otočit na přední displej pro lepší selfie?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Pomocí zadního fotoaparátu pořiďte širší fotku s vyšším rozlišením."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Tato obrazovka se vypne"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozkládání rozkládacího zařízení"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Otáčení rozkládacího zařízení"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Zbývá <xliff:g id="PERCENTAGE">%s</xliff:g> baterie"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Připojte dotykové pero k nabíječce"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Slabá baterie dotykového pera"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Z tohoto profilu nelze volat"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Vaše pracovní zásady vám umožňují telefonovat pouze z pracovního profilu"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Přepnout na pracovní profil"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zavřít"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index f64587e..7c4e92c 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Nederste kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Højre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Screenshots, der tages via arbejdsprofilen, gemmer i appen <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Skærmoptagelse"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Behandler skærmoptagelse"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Konstant notifikation om skærmoptagelse"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatisk"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ingen lyd eller vibration"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ingen lyd eller vibration, og den vises længere nede i samtalesektionen"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan ringe eller vibrere baseret på telefonens indstillinger"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan ringe eller vibrere baseret på telefonens indstillinger. Samtaler fra <xliff:g id="APP_NAME">%1$s</xliff:g> vises som standard i bobler."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan ringe eller vibrere baseret på enhedens indstillinger"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan ringe eller vibrere baseret på enhedens indstillinger. Samtaler fra <xliff:g id="APP_NAME">%1$s</xliff:g> vises som standard i bobler."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Få systemet til at afgøre, om denne notifikation skal vibrere eller afspille en lyd"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Angivet som Standard"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Angivet som Lydløs"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Vælg en app for at tilføje styring"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# styringselement er tilføjet.}one{# styringselement er tilføjet.}other{# styringselementer er tilføjet.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Fjernet"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Angivet som favorit"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Angivet som favorit. Position <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Fjernet fra favoritter"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Fortryd"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flyt enheden tættere på for at afspille på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Ryk tættere på <xliff:g id="DEVICENAME">%1$s</xliff:g> for at afspille her"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ryk tættere på for at afspille på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"For at afspille her skal enheden tættere på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Afspilles på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Noget gik galt. Prøv igen."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Indlæser"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Tjek appen"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Ikke fundet"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Styringselement ikke tilgængeligt"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Der opstod en fejl. Prøv igen"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Tilføj styring"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Rediger styring"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tilføj medieudgange"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Der er valgt 1 enhed"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Lydstyrke"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Højttalere og skærme"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Foreslåede enheder"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Sådan fungerer udsendelser"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Udsendelse"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personer i nærheden, som har kompatible Bluetooth-enheder, kan lytte til det medie, du udsender"</string>
@@ -1012,8 +1021,17 @@
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Fold telefonen ud for at tage en bedre selfie"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vil du bruge frontkameraet for at få bedre selfie?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Brug bagsidekameraet for at få et bredere billede med højere opløsning."</string>
-    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ *Denne skærm slukkes"</b></string>
+    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Denne skærm slukkes"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldbar enhed foldes ud"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldbar enhed vendes om"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteri tilbage"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Slut din styluspen til en oplader"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Lavt batteriniveau på styluspen"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Du kan ikke ringe fra denne profil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Din arbejdspolitik tillader kun, at du kan foretage telefonopkald fra arbejdsprofilen"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Skift til arbejdsprofil"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Luk"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index bd4c587..2b975bc 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Unterer Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linker Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechter Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Mit einem Arbeitsprofil aufgenommene Screenshots werden in der App „<xliff:g id="APP">%1$s</xliff:g>“ gespeichert"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dateien"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Bildschirmaufzeichnung"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Bildschirmaufzeichnung…"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Fortlaufende Benachrichtigung für eine Bildschirmaufzeichnung"</string>
@@ -98,8 +100,8 @@
     <string name="screenrecord_description" msgid="1123231719680353736">"Beim Aufnehmen kann das Android-System vertrauliche Informationen erfassen, die auf deinem Bildschirm angezeigt oder von deinem Gerät wiedergegeben werden. Das können Passwörter, Zahlungsinformationen, Fotos, Nachrichten und Audioinhalte sein."</string>
     <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Gesamten Bildschirm aufnehmen"</string>
     <string name="screenrecord_option_single_app" msgid="5954863081500035825">"Eine einzelne App aufnehmen"</string>
-    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Während der Aufnahme hat Android Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
-    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Während der Aufnahme einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"Während der Aufnahme hat Android Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher vorsichtig mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"Während der Aufnahme einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher vorsichtig mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen."</string>
     <string name="screenrecord_start_recording" msgid="348286842544768740">"Aufnahme starten"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"Audio aufnehmen"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Audio des Geräts"</string>
@@ -374,13 +376,13 @@
     <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Zulassen, dass <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Inhalte teilt oder aufnimmt?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Gesamter Bildschirm"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Eine einzelne App"</string>
-    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Beim Teilen, Aufnehmen oder Übertragen hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Beim Teilen, Aufnehmen oder Übertragen einer App hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Beim Teilen, Aufnehmen oder Übertragen hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher vorsichtig mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Beim Teilen, Aufnehmen oder Übertragen einer App hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher vorsichtig mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Weiter"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App teilen oder aufnehmen"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Dieser App das Teilen oder Aufnehmen erlauben?"</string>
-    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Beim Teilen, Aufnehmen oder Übertragen hat diese App Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
-    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Beim Teilen, Aufnehmen oder Übertragen einer App hat diese App Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Beim Teilen, Aufnehmen oder Übertragen hat diese App Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher vorsichtig mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Beim Teilen, Aufnehmen oder Übertragen einer App hat diese App Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher vorsichtig mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen."</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Vom IT-Administrator blockiert"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Bildschirmaufnahme ist durch die Geräterichtlinien deaktiviert"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatisch"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Kein Ton und keine Vibration"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Kein Ton und keine Vibration, erscheint weiter unten im Bereich „Unterhaltungen“"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kann klingeln oder vibrieren, abhängig von den Telefoneinstellungen"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kann klingeln oder vibrieren, je nach Telefoneinstellungen. Unterhaltungen von <xliff:g id="APP_NAME">%1$s</xliff:g> werden standardmäßig als Bubble angezeigt."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Kann je nach Geräteeinstellungen klingeln oder vibrieren"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kann je nach Geräteeinstellungen klingeln oder vibrieren. Unterhaltungen von <xliff:g id="APP_NAME">%1$s</xliff:g> werden standardmäßig als Bubble angezeigt."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Das System entscheiden lassen, ob bei dieser Benachrichtigung ein Ton oder eine Vibration ausgegeben wird"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status&lt;/b&gt;: auf „Standard“ hochgestuft"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status&lt;/b&gt;: auf „Lautlos“ herabgestuft"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"App zum Hinzufügen von Steuerelementen auswählen"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# Steuerelement hinzugefügt.}other{# Steuerelemente hinzugefügt.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Entfernt"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Zu Favoriten hinzugefügt"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Zu Favoriten hinzugefügt, Position <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Aus Favoriten entfernt"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergeben"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> über <xliff:g id="APP_LABEL">%2$s</xliff:g> wiedergeben"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Rückgängig machen"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Gehe für die Wiedergabe näher an <xliff:g id="DEVICENAME">%1$s</xliff:g> heran"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Platziere für die Wiedergabe dein Gerät näher an „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string>
-    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Wird auf „<xliff:g id="DEVICENAME">%1$s</xliff:g>“ abgespielt"</string>
-    <string name="media_transfer_failed" msgid="7955354964610603723">"Es gab ein Problem. Versuch es noch einmal."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Gehe für die Wiedergabe näher an „<xliff:g id="DEVICENAME">%1$s</xliff:g>“ heran"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Für eine Wiedergabe auf diesem Gerät muss es näher bei <xliff:g id="DEVICENAME">%1$s</xliff:g> sein"</string>
+    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Wiedergabe läuft auf „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string>
+    <string name="media_transfer_failed" msgid="7955354964610603723">"Ein Fehler ist aufgetreten. Versuch es noch einmal."</string>
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Wird geladen"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"Tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv – sieh in der App nach"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nicht gefunden"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Steuerelement nicht verfügbar"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Fehler – versuch es noch mal"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Steuerelemente hinzufügen"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Steuerelemente bearbeiten"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ausgabegeräte hinzufügen"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ein Gerät ausgewählt"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Lautstärke"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Lautsprecher &amp; Displays"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Vorgeschlagene Geräte"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Funktionsweise von Nachrichten an alle"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Nachricht an alle"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personen, die in der Nähe sind und kompatible Bluetooth-Geräten haben, können sich die Medien anhören, die du per Nachricht an alle sendest"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Dieses Display wird dann ausgeschaltet"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Faltbares Gerät wird geöffnet"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Faltbares Gerät wird umgeklappt"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akku bei <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Schließe deinen Eingabestift an ein Ladegerät an"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus-Akkustand niedrig"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Keine Anrufe über dieses Profil möglich"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Gemäß den Arbeitsrichtlinien darfst du nur über dein Arbeitsprofil telefonieren"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Zum Arbeitsprofil wechseln"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Schließen"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 05cf06c..47bcfcb 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Κάτω όριο <xliff:g id="PERCENT">%1$d</xliff:g> τοις εκατό"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Αριστερό όριο <xliff:g id="PERCENT">%1$d</xliff:g> τοις εκατό"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Δεξί όριο <xliff:g id="PERCENT">%1$d</xliff:g> τοις εκατό"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Τα στιγμιότυπα οθόνης εργασίας αποθηκεύονται στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Αρχεία"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Εγγραφή οθόνης"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Επεξεργασία εγγραφής οθόνης"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ειδοποίηση σε εξέλιξη για μια περίοδο λειτουργίας εγγραφής οθόνης"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Αυτόματο"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Χωρίς ήχο ή δόνηση"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Χωρίς ήχο ή δόνηση και εμφανίζεται χαμηλά στην ενότητα συζητήσεων"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων του τηλεφώνου"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων του τηλεφώνου. Οι συζητήσεις από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> εμφανίζονται σε συννεφάκι από προεπιλογή."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων συσκευής"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Ενδέχεται να κουδουνίζει ή να δονείται βάσει των ρυθμίσεων συσκευής. Οι συζητήσεις από την εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> εμφανίζονται σε συννεφάκι από προεπιλογή."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Επιτρέψτε στο σύστημα να αποφασίσει αν αυτή η ειδοποίηση θα αναπαράγει έναν ήχο ή θα ενεργοποιήσει τη δόνηση της συσκευής"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Κατάσταση:&lt;/b&gt; Προάχθηκε σε Προεπιλογή"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Κατάσταση:&lt;/b&gt; Υποβιβάστηκε σε Αθόρυβη"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Επιλογή εφαρμογής για προσθήκη στοιχείων ελέγχου"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Προστέθηκε # στοιχείο ελέγχου.}other{Προστέθηκαν # στοιχεία ελέγχου.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Καταργήθηκε"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Προσθήκη <xliff:g id="APPNAME">%s</xliff:g>;"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Όταν προσθέσετε την εφαρμογή <xliff:g id="APPNAME">%s</xliff:g>, μπορεί να προσθέσει στοιχεία ελέγχου και περιεχόμενο σε αυτό το πλαίσιο. Σε ορισμένες εφαρμογές, μπορείτε να επιλέξετε ποια στοιχεία ελέγχου θα εμφανίζονται εδώ."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Προστέθηκε στα αγαπημένα"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Προστέθηκε στα αγαπημένα, στη θέση <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Αφαιρέθηκε από τα αγαπημένα"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Αναίρεση"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Πλησιάστε για αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Μετακινηθείτε πιο κοντά στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g> για αναπαραγωγή εδώ"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Για να γίνει αναπαραγωγή εδώ, μετακινηθείτε πιο κοντά στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Παρουσιάστηκε κάποιο πρόβλημα. Δοκιμάστε ξανά."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Φόρτωση"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Ανενεργό, έλεγχος εφαρμογής"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Δεν βρέθηκε."</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Μη διαθέσιμο στοιχείο ελέγχου"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Σφάλμα, προσπαθήστε ξανά."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Προσθήκη στοιχείων ελέγχου"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Επεξεργασία στοιχείων ελέγχου"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Προσθήκη εφαρμογής"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Προσθήκη εξόδων"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Ομάδα"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Επιλέχτηκε 1 συσκευή"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ένταση ήχου"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Ηχεία και οθόνες"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Προτεινόμενες συσκευές"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Πώς λειτουργεί η μετάδοση"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Μετάδοση"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Οι άνθρωποι με συμβατές συσκευές Bluetooth που βρίσκονται κοντά σας μπορούν να ακούσουν το μέσο που μεταδίδετε."</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* Αυτή η οθόνη θα απενεργοποιηθεί"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Αναδιπλούμενη συσκευή που ξεδιπλώνει"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Αναδιπλούμενη συσκευή που διπλώνει"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Απομένει το <xliff:g id="PERCENTAGE">%s</xliff:g> της μπαταρίας"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Συνδέστε τη γραφίδα σε έναν φορτιστή"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Χαμηλή στάθμη μπαταρίας γραφίδας"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Βιντεοκάμερα"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Δεν είναι δυνατή η κλήση από αυτό το προφίλ"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Η πολιτική εργασίας σάς επιτρέπει να πραγματοποιείτε τηλεφωνικές κλήσεις μόνο από το προφίλ εργασίας σας."</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Εναλλαγή σε προφίλ εργασίας"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Κλείσιμο"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 7c90280..2b5e8f9 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Work screenshots are saved in the <xliff:g id="APP">%1$s</xliff:g> app"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatic"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"No sound or vibration"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No sound or vibration and appears lower in conversation section"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"May ring or vibrate based on device settings"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"May ring or vibrate based on device settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; promoted to default"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; demoted to silent"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Removed"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Add <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"When you add <xliff:g id="APPNAME">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favourited"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favourited, position <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavourited"</string>
@@ -850,10 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g> to play here"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string>
     <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
@@ -863,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Add app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
@@ -877,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers &amp; displays"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Suggested devices"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media that you\'re broadcasting"</string>
@@ -1014,5 +1021,13 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ This screen will turn off"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Video camera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Can\'t call from this profile"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Your work policy allows you to make phone calls only from the work profile"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Switch to work profile"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Close"</string>
+    <string name="lock_screen_settings" msgid="9197175446592718435">"Lock screen settings"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f195c64..77452b5 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Work screenshots are saved in the <xliff:g id="APP">%1$s</xliff:g> app"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatic"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"No sound or vibration"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No sound or vibration and appears lower in conversation section"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"May ring or vibrate based on device settings"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"May ring or vibrate based on device settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Promoted to Default"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Demoted to Silent"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Removed"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Add <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"When you add <xliff:g id="APPNAME">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favorited"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favorited, position <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavorited"</string>
@@ -850,10 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g> to play here"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string>
     <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
@@ -863,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Add app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device selected"</string>
@@ -877,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers &amp; Displays"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Suggested Devices"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media you\'re broadcasting"</string>
@@ -1014,5 +1021,13 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ This screen will turn off"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Video camera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Can\'t call from this profile"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Your work policy allows you to make phone calls only from the work profile"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Switch to work profile"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Close"</string>
+    <string name="lock_screen_settings" msgid="9197175446592718435">"Lock screen settings"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 7c90280..2b5e8f9 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Work screenshots are saved in the <xliff:g id="APP">%1$s</xliff:g> app"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatic"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"No sound or vibration"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No sound or vibration and appears lower in conversation section"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"May ring or vibrate based on device settings"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"May ring or vibrate based on device settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; promoted to default"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; demoted to silent"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Removed"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Add <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"When you add <xliff:g id="APPNAME">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favourited"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favourited, position <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavourited"</string>
@@ -850,10 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g> to play here"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string>
     <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
@@ -863,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Add app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
@@ -877,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers &amp; displays"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Suggested devices"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media that you\'re broadcasting"</string>
@@ -1014,5 +1021,13 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ This screen will turn off"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Video camera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Can\'t call from this profile"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Your work policy allows you to make phone calls only from the work profile"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Switch to work profile"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Close"</string>
+    <string name="lock_screen_settings" msgid="9197175446592718435">"Lock screen settings"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 7c90280..2b5e8f9 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Work screenshots are saved in the <xliff:g id="APP">%1$s</xliff:g> app"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatic"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"No sound or vibration"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No sound or vibration and appears lower in conversation section"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"May ring or vibrate based on device settings"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"May ring or vibrate based on device settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; promoted to default"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; demoted to silent"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Removed"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Add <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"When you add <xliff:g id="APPNAME">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favourited"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favourited, position <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavourited"</string>
@@ -850,10 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g> to play here"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Playing on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string>
     <string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
@@ -863,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Add controls"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Add app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
@@ -877,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers &amp; displays"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Suggested devices"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media that you\'re broadcasting"</string>
@@ -1014,5 +1021,13 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ This screen will turn off"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Video camera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Can\'t call from this profile"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Your work policy allows you to make phone calls only from the work profile"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Switch to work profile"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Close"</string>
+    <string name="lock_screen_settings" msgid="9197175446592718435">"Lock screen settings"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 03659b4..173b905 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‏‏‎‏‎‏‎‏‏‏‎‎Bottom boundary ‎‏‎‎‏‏‎<xliff:g id="PERCENT">%1$d</xliff:g>‎‏‎‎‏‏‏‎ percent‎‏‎‎‏‎"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‎Left boundary ‎‏‎‎‏‏‎<xliff:g id="PERCENT">%1$d</xliff:g>‎‏‎‎‏‏‏‎ percent‎‏‎‎‏‎"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎Right boundary ‎‏‎‎‏‏‎<xliff:g id="PERCENT">%1$d</xliff:g>‎‏‎‎‏‏‏‎ percent‎‏‎‎‏‎"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎Work screenshots are saved in the ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ app‎‏‎‎‏‎"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‎Files‎‏‎‎‏‎"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‎Screen Recorder‎‏‎‎‏‎"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎‎‏‎‎Processing screen recording‎‏‎‎‏‎"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‏‏‎‎‎‏‏‏‏‎‏‏‏‏‎Ongoing notification for a screen record session‎‏‎‎‏‎"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎Automatic‎‏‎‎‏‎"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‎‏‎‏‏‎‎No sound or vibration‎‏‎‎‏‎"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎No sound or vibration and appears lower in conversation section‎‏‎‎‏‎"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‏‎‎May ring or vibrate based on phone settings‎‏‎‎‏‎"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎May ring or vibrate based on phone settings. Conversations from ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ bubble by default.‎‏‎‎‏‎"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎May ring or vibrate based on device settings‎‏‎‎‏‎"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‎‎‎‎‏‎‎‎‎‎May ring or vibrate based on device settings. Conversations from ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ bubble by default.‎‏‎‎‏‎"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‏‎‏‏‎Have the system determine if this notification should make sound or vibration‎‏‎‎‏‎"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎&lt;b&gt;Status:&lt;/b&gt; Promoted to Default‎‏‎‎‏‎"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‎‏‎‏‎‏‎‎‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‎&lt;b&gt;Status:&lt;/b&gt; Demoted to Silent‎‏‎‎‏‎"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‎Choose app to add controls‎‏‎‎‏‎"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‏‏‎# control added.‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‏‏‎# controls added.‎‏‎‎‏‎}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎Removed‎‏‎‎‏‎"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎Add ‎‏‎‎‏‏‎<xliff:g id="APPNAME">%s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎When you add ‎‏‎‎‏‏‎<xliff:g id="APPNAME">%s</xliff:g>‎‏‎‎‏‏‏‎, it can add controls and content to this panel. In some apps, you can choose which controls show up here.‎‏‎‎‏‎"</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‎‎‎‎‎‎‏‎Favorited‎‏‎‎‏‎"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‎Favorited, position ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‎‏‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎Unfavorited‎‏‎‎‏‎"</string>
@@ -850,10 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‎Play ‎‏‎‎‏‏‎<xliff:g id="SONG_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ from ‎‏‎‎‏‏‎<xliff:g id="APP_LABEL">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎Undo‎‏‎‎‏‎"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‎‎‎‎Move closer to play on ‎‏‎‎‏‏‎<xliff:g id="DEVICENAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎Move closer to ‎‏‎‎‏‏‎<xliff:g id="DEVICENAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to play here‎‏‎‎‏‎"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎To play here, move closer to ‎‏‎‎‏‏‎<xliff:g id="DEVICENAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‏‎‏‎‎Playing on ‎‏‎‎‏‏‎<xliff:g id="DEVICENAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‎‏‏‎Something went wrong. Try again.‎‏‎‎‏‎"</string>
     <string name="media_transfer_loading" msgid="5544017127027152422">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‎‏‏‎‎Loading‎‏‎‎‏‎"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‏‏‏‎‎‏‎tablet‎‏‎‎‏‎"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎Inactive, check app‎‏‎‎‏‎"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎Not found‎‏‎‎‏‎"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎‎‏‎‏‎‎Control is unavailable‎‏‎‎‏‎"</string>
@@ -863,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎Error, try again‎‏‎‎‏‎"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‎‎Add controls‎‏‎‎‏‎"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‎Edit controls‎‏‎‎‏‎"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‎‏‎‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‏‏‎‎‏‎‎‎‏‎Add app‎‏‎‎‏‎"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎Add outputs‎‏‎‎‏‎"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‎Group‎‏‎‎‏‎"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎1 device selected‎‏‎‎‏‎"</string>
@@ -877,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎Volume‎‏‎‎‏‎"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%1$d</xliff:g>‎‏‎‎‏‏‏‎%%‎‏‎‎‏‎"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎Speakers &amp; Displays‎‏‎‎‏‎"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‎‎Suggested Devices‎‏‎‎‏‎"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎How broadcasting works‎‏‎‎‏‎"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‎‏‏‏‎Broadcast‎‏‎‎‏‎"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‏‎‎People near you with compatible Bluetooth devices can listen to the media you\'re broadcasting‎‏‎‎‏‎"</string>
@@ -1014,5 +1021,13 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎✱ This screen will turn off‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎Foldable device being unfolded‎‏‎‎‏‎"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎Foldable device being flipped around‎‏‎‎‏‎"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%s</xliff:g>‎‏‎‎‏‏‏‎ battery remaining‎‏‎‎‏‎"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎Connect your stylus to a charger‎‏‎‎‏‎"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‎Stylus battery low‎‏‎‎‏‎"</string>
+    <string name="video_camera" msgid="7654002575156149298">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎Video camera‎‏‎‎‏‎"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‎‏‎‏‏‎Can\'t call from this profile‎‏‎‎‏‎"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎Your work policy allows you to make phone calls only from the work profile‎‏‎‎‏‎"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‎‎Switch to work profile‎‏‎‎‏‎"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎Close‎‏‎‎‏‎"</string>
+    <string name="lock_screen_settings" msgid="9197175446592718435">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‏‎‎‏‏‎‎‎‏‏‎Lock screen settings‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 2dc01f1..e5e20ba 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Límite inferior: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Límite izquierdo: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Límite derecho: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Las capturas de pantalla de trabajo se guardan en la app de <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Grabadora de pantalla"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Procesando grabación pantalla"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación constante para una sesión de grabación de pantalla"</string>
@@ -375,7 +377,7 @@
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Pantalla completa"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola app"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Cuando compartas, grabes o transmitas contenido, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo aquel que sea visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o transmitas una app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo el contenido que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o transmitas una app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo el contenido que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con las contraseñas, los detalles de pagos, los mensajes y otra información sensible."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una app"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"¿Quieres permitir que esta app comparta o grabe tu pantalla?"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sin sonido ni vibración"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"No suena ni vibra, y aparece en la parte inferior de la sección de conversaciones."</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Puede sonar o vibrar en función de la configuración del teléfono."</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Puede sonar o vibrar en función de la configuración del teléfono. Conversaciones de la burbuja de <xliff:g id="APP_NAME">%1$s</xliff:g> de forma predeterminada."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Puede sonar o vibrar según la configuración del dispositivo"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Puede sonar o vibrar según la configuración del dispositivo. Conversaciones de la burbuja de <xliff:g id="APP_NAME">%1$s</xliff:g> de forma predeterminada."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Dejar que el sistema determine si esta notificación debe emitir un sonido o una vibración"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Estado:&lt;/b&gt; Se promovió a Predeterminada"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Estado:&lt;/b&gt; Descendió de nivel a Silenciada"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Elige la app para agregar los controles"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Se agregó # control.}many{Se agregaron # controles.}other{Se agregaron # controles.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Quitados"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"¿Quieres agregar <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Si agregas la app de <xliff:g id="APPNAME">%s</xliff:g>, se incluirán controles y contenido en este panel. Algunas apps te permiten elegir qué controles mostrar aquí."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Está en favoritos"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Está en favoritos en la posición <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"No está en favoritos"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducir <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Acércate para reproducir en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para reproducir aquí"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducir aquí, acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Se produjo un error. Vuelve a intentarlo."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Cargando"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Verifica la app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"No se encontró"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"El control no está disponible"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Error. Vuelve a intentarlo."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Agregar controles"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Agregar app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Agregar salidas"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Se seleccionó 1 dispositivo"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volumen"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Bocinas y pantallas"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la transmisión"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Transmisión"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Las personas cercanas con dispositivos Bluetooth compatibles pueden escuchar el contenido multimedia que transmites"</string>
@@ -1010,10 +1016,19 @@
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Cancelar"</string>
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Girar ahora"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Despliega el teléfono para tomar una selfie mejor"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"¿Cambiar a pantalla frontal para mejores selfies?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"¿Girar a pantalla frontal para mejores selfies?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Usa la cámara trasera para tomar una foto más amplia y con mejor resolución."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta pantalla se apagará"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable siendo desplegado"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable siendo girado"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de batería restante"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta tu pluma stylus a un cargador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"La pluma stylus tiene poca batería"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videocámara"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"No se puede llamar desde este perfil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Tu política del trabajo te permite hacer llamadas telefónicas solo desde el perfil de trabajo"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Cambiar al perfil de trabajo"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Cerrar"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index c2443da..41d9c9e 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite inferior"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite izquierdo"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite derecho"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Las capturas de pantalla de trabajo se guardan en la aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Archivos"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Grabación de pantalla"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Procesando grabación de pantalla"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación continua de una sesión de grabación de la pantalla"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sin sonido ni vibración"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sin sonido ni vibración, y se muestra más abajo en la sección de conversaciones"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Puede sonar o vibrar según los ajustes del teléfono"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Puede sonar o vibrar según los ajustes del teléfono. Las conversaciones de <xliff:g id="APP_NAME">%1$s</xliff:g> aparecen como burbujas de forma predeterminada."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Puede sonar o vibrar según los ajustes del dispositivo"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Puede sonar o vibrar según los ajustes del dispositivo. Las conversaciones de <xliff:g id="APP_NAME">%1$s</xliff:g> aparecen como burbujas de forma predeterminada."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Haz que el sistema determine si con esta notificación el dispositivo debe sonar o vibrar"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Estado:&lt;/b&gt; cambio a Predeterminado"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Estado:&lt;/b&gt; cambio a Silencio"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Elige una aplicación para añadir controles"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control añadido.}many{# controles añadidos.}other{# controles añadidos.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Quitado"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Añadido a favoritos"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Añadido a favoritos (posición <xliff:g id="NUMBER">%d</xliff:g>)"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Quitado de favoritos"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para que se reproduzca en ese dispositivo"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para jugar aquí"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para reproducir contenido ahí"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducirlo, acércate al dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Se ha producido un error. Inténtalo de nuevo."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Cargando"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo, comprobar aplicación"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"No se ha encontrado"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Control no disponible"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Error: Vuelve a intentarlo"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Añadir controles"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Añadir dispositivos de salida"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo seleccionado"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volumen"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altavoces y pantallas"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Sugerencias de dispositivos"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la emisión"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Emisión"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Las personas cercanas con dispositivos Bluetooth compatibles pueden escuchar el contenido multimedia que emites"</string>
@@ -1008,12 +1017,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Al menos un dispositivo debe estar disponible"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Mantén pulsado el acceso directo"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Cancelar"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Girar ahora"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Usar ahora"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Despliega el teléfono para hacer un selfie mejor"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"¿Usar pantalla frontal para hacer mejores selfies?"</string>
-    <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Usa la cámara trasera para hacer una foto más amplia y con mayor resolución."</string>
+    <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Usa la cámara trasera para hacer fotos más amplias y con mayor resolución."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta pantalla se apagará"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable desplegándose"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable mostrado desde varios ángulos"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batería restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta tu lápiz óptico a un cargador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Batería del lápiz óptico baja"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videocámara"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"No se puede llamar desde este perfil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Tu política del trabajo solo te permite hacer llamadas telefónicas desde el perfil de trabajo"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Cambiar al perfil de trabajo"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Cerrar"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index b31030a..225812c 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alapiir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasak piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Parem piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Töö ekraanipildid salvestatakse rakendusse <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Ekraanisalvesti"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekraanisalvestuse töötlemine"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pooleli märguanne ekraanikuva salvestamise seansi puhul"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automaatne"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ilma heli ja vibreerimiseta"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ilma heli ja vibreerimiseta, kuvatakse vestluste jaotises allpool"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Võib telefoni seadete põhjal heliseda või vibreerida"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Võib telefoni seadete põhjal heliseda või vibreerida. Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> vestlused kuvatakse vaikimisi mullis."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Võib seadme seadete põhjal heliseda või vibreerida"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Võib seadme seadete põhjal heliseda või vibreerida. Rakenduse <xliff:g id="APP_NAME">%1$s</xliff:g> vestlused kuvatakse vaikimisi mullis."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Laske süsteemil määrata, kas selle märguande puhul peaks esitama heli või vibreerima"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Olek:&lt;/b&gt; määrati prioriteet Vaikimisi"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Olek:&lt;/b&gt; määrati prioriteet Vaikne"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Valige juhtelementide lisamiseks rakendus"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Lisati # juhtnupp.}other{Lisati # juhtnuppu.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Eemaldatud"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Lisatud lemmikuks"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Lisatud lemmikuks, positsioon <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Eemaldatud lemmikute hulgast"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Võta tagasi"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Teisaldage lähemale, et seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g> esitada"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Siin esitamiseks liigutage seadmele <xliff:g id="DEVICENAME">%1$s</xliff:g> lähemale"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Liikuge lähemale, et seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g> esitada"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Siin esitamiseks minge seadmele <xliff:g id="DEVICENAME">%1$s</xliff:g> lähemale"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Esitatakse seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Midagi läks valesti. Proovige uuesti."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Laadimine"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tahvelarvuti"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Passiivne, vaadake rakendust"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Ei leitud"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Juhtelement pole saadaval"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Ilmnes viga, proovige uuesti"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Lisa juhtelemente"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Muuda juhtelemente"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Väljundite lisamine"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 seade on valitud"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Helitugevus"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Kõlarid ja ekraanid"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Soovitatud seadmed"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kuidas ülekandmine toimib?"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Ülekanne"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Teie läheduses olevad inimesed, kellel on ühilduvad Bluetooth-seadmed, saavad kuulata teie ülekantavat meediat"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ See ekraan lülitatakse välja"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Volditava seadme lahtivoltimine"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Volditava seadme ümberpööramine"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akutase on <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ühendage elektronpliiats laadijaga"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Elektronpliiatsi akutase on madal"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokaamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Sellelt profiililt ei saa helistada"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Teie töökoha eeskirjad lubavad teil helistada ainult tööprofiililt"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Lülitu tööprofiilile"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Sule"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index de9c75f..37a66b0 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Beheko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Ezkerreko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Eskuineko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Laneko pantaila-argazkiak <xliff:g id="APP">%1$s</xliff:g> aplikazioan gordetzen dira"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxategiak"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Pantaila-grabagailua"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Pantaila-grabaketa prozesatzen"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pantailaren grabaketa-saioaren jakinarazpen jarraitua"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatikoa"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ez du tonurik jotzen edo dar-dar egiten"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ez du tonurik jotzen edo dar-dar egiten, eta elkarrizketen atalaren behealdean agertzen da"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Tonua joko du, edo dar-dar egingo, telefonoaren ezarpenen arabera"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Tonua joko du, edo dar-dar egingo, telefonoaren ezarpenen arabera. Modu lehenetsian, <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioko elkarrizketak burbuila gisa agertzen dira."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Baliteke tonua jotzea edo dardara egitea, gailuaren ezarpenen arabera"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Baliteke tonua jotzea edo dardara egitea, gailuaren ezarpenen arabera. Modu lehenetsian, <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioko elkarrizketak burbuila gisa agertzen dira."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Ezarri sistemak zehaztu dezala jakinarazpen honek soinua edo dardara egin behar duen ala ez"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"Lehenetsi gisa ezarri da &lt;b&gt;egoera:&lt;/b&gt;"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"Soinurik gabeko modura aldatu da &lt;b&gt;egoera:&lt;/b&gt;"</string>
@@ -690,7 +692,7 @@
     <string name="tuner_lock_screen" msgid="2267383813241144544">"Pantaila blokeatua"</string>
     <string name="thermal_shutdown_title" msgid="2702966892682930264">"Beroegi egoteagatik itzali da"</string>
     <string name="thermal_shutdown_message" msgid="6142269839066172984">"Ohi bezala ari da funtzionatzen telefonoa orain.\nInformazio gehiago lortzeko, sakatu hau."</string>
-    <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Telefonoa gehiegi berotu da, eta itzali egin da tenperatura jaisteko. Orain, ohiko moduan dabil.\n\nBerotzearen zergati posibleak:\n	• Baliabide asko behar dituzten aplikazioak erabiltzea (adib., jokoak, bideoak edo nabigazio-aplikazioak).\n	• Fitxategi handiak deskargatu edo kargatzea.\n	• Telefonoa giro beroetan erabiltzea."</string>
+    <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Telefonoa gehiegi berotu da, eta itzali egin da tenperatura jaisteko. Orain, ohiko moduan dabil.\n\nBerotzearen zergati posibleak:\n	• Baliabide asko behar dituzten aplikazioak erabiltzea (adib., bideojokoak, bideoak edo nabigazio-aplikazioak).\n	• Fitxategi handiak deskargatu edo kargatzea.\n	• Telefonoa giro beroetan erabiltzea."</string>
     <string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ikusi zaintzeko urratsak"</string>
     <string name="high_temp_title" msgid="2218333576838496100">"Berotzen ari da telefonoa"</string>
     <string name="high_temp_notif_message" msgid="1277346543068257549">"Eginbide batzuk ezingo dira erabili telefonoa hoztu arte.\nInformazio gehiago lortzeko, sakatu hau."</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Aukeratu aplikazio bat kontrolatzeko aukerak gehitzeko"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Kontrolatzeko # aukera gehitu da.}other{Kontrolatzeko # aukera gehitu dira.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Kenduta"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> gehitu nahi duzu?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> gehitzen duzunean, kontrolatzeko aukerak eta edukia gehi ditzake panelean. Aplikazio batzuetan, hemen zein kontrolatzeko aukera agertzen diren aukera dezakezu."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Gogokoetan dago"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"<xliff:g id="NUMBER">%d</xliff:g>. gogokoa da"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Ez dago gogokoetan"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> bidez"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Desegin"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Gertura ezazu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailuan erreproduzitzeko"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Gerturatu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailura bertan erreproduzitzen ari dena hemen erreproduzitzeko"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Hemen erreproduzitzeko, hurbildu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailura"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> gailuan erreproduzitzen"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Arazoren bat izan da. Saiatu berriro."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Kargatzen"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tableta"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktibo; egiaztatu aplikazioa"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Ez da aurkitu"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Ez dago erabilgarri kontrolatzeko aukera"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Errorea. Saiatu berriro."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Gehitu aukerak"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editatu aukerak"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Gehitu aplikazio bat"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Gehitu irteerak"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Taldea"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 gailu hautatu da"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Bolumena"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%% <xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Bozgorailuak eta pantailak"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Iradokitako gailuak"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Nola funtzionatzen dute iragarpenek?"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Iragarri"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Bluetooth bidezko gailu bateragarriak dituzten inguruko pertsonek iragartzen ari zaren multimedia-edukia entzun dezakete"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Pantaila itzali egingo da"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Gailu tolesgarria zabaltzen"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Gailu tolesgarria biratzen"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateriaren <xliff:g id="PERCENTAGE">%s</xliff:g> geratzen da"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Konektatu arkatza kargagailu batera"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Arkatzak bateria gutxi du"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Bideokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ezin duzu deitu profil honetatik"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Deiak laneko profiletik soilik egiteko baimena ematen dizute laneko gidalerroek"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Aldatu laneko profilera"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Itxi"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 87aed94..a057ad9 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"مرز پایین <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"مرز سمت چپ <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"مرز سمت راست <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"نماگرفت‌های نمایه کاری در برنامه <xliff:g id="APP">%1$s</xliff:g> ذخیره می‌شوند"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"ضبط‌کننده صفحه‌نمایش"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"درحال پردازش ضبط صفحه‌نمایش"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"اعلان درحال انجام برای جلسه ضبط صفحه‌نمایش"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"خودکار"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"بدون صدا یا لرزش"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"بدون صدا و لرزش در پایین بخش مکالمه نشان داده می‌شود"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"بسته به تنظیمات ممکن است تلفن زنگ بزند یا لرزش داشته باشد"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"بسته به تنظیمات ممکن است تلفن زنگ بزند یا لرزش داشته باشد. مکالمه‌های <xliff:g id="APP_NAME">%1$s</xliff:g> به‌طور پیش‌فرض در حبابک نشان داده می‌شوند."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"بسته به تنظیمات دستگاه ممکن است زنگ بزند یا بلرزد"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"بسته به تنظیمات دستگاه ممکن است زنگ بزند یا بلرزد. مکالمه‌های <xliff:g id="APP_NAME">%1$s</xliff:g> به‌طور پیش‌فرض در حبابک نشان داده می‌شوند."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"سیستم را تنظیم کنید که تشخیص دهد اعلان صدا و لرزش داشته باشد یا نه"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"‏&lt;b&gt;وضعیت:&lt;/b&gt; به «پیش‌فرض» ارتقا یافت"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"‏&lt;b&gt;وضعیت:&lt;/b&gt; به «بی‌صدا» تنزل یافت"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"انتخاب برنامه برای افزودن کنترل‌ها"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# کنترل اضافه شد.}one{# کنترل اضافه شد.}other{# کنترل اضافه شد.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"حذف شد"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"به موارد دلخواه اضافه شد"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"اضافه‌شده به موارد دلخواه، جایگاه <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"حذف‌شده از موارد دلخواه"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%2$s</xliff:g> پخش کنید"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"واگرد"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"برای پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g> به دستگاه نزدیک‌تر شوید"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"برای پخش در اینجا، به <xliff:g id="DEVICENAME">%1$s</xliff:g> نزدیک‌تر شوید"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"برای پخش در اینجا، به <xliff:g id="DEVICENAME">%1$s</xliff:g> نزدیک‌تر شوید"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"درحال پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"مشکلی پیش آمد. دوباره امتحان کنید."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"درحال بار کردن"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"رایانه لوحی"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"غیرفعال، برنامه را بررسی کنید"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"پیدا نشد"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"کنترل دردسترس نیست"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"خطا، دوباره امتحان کنید"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"افزودن کنترل‌ها"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ویرایش کنترل‌ها"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"افزودن خروجی"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"گروه"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"۱ دستگاه انتخاب شد"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"میزان صدا"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"بلندگوها و نمایشگرها"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"دستگاه‌های پیشنهادی"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"همه‌فرتستی چطور کار می‌کند"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"همه‌فرستی"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"‏افرادی که در اطرافتان دستگاه‌های Bluetooth سازگار دارند می‌توانند به رسانه‌ای که همه‌فرستی می‌کنید گوش کنند"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ این صفحه‌نمایش خاموش خواهد شد"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"دستگاه تاشو درحال باز شدن"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"دستگاه تاشو درحال چرخش به اطراف"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> باتری باقی مانده است"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"قلم را به شارژر وصل کنید"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"باتری قلم ضعیف است"</string>
+    <string name="video_camera" msgid="7654002575156149298">"دوربین ویدیویی"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"نمی‌توانید از این نمایه تماس بگیرید"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"خط‌مشی کاری شما فقط به برقراری تماس ازطریق نمایه کاری اجازه می‌دهد"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"رفتن به نمایه کاری"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"بستن"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index f7bb7f7..c4f5571 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alareuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasen reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Oikea reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Työprofiilin kuvakaappaukset tallennetaan sovellukseen: <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Näytön tallentaja"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Näytön tallennusta käsitellään"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pysyvä ilmoitus näytön tallentamisesta"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automaattinen"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ei ääntä tai värinää"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ei ääntä tai värinää ja näkyy alempana keskusteluosiossa"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Voi soida tai väristä puhelimen asetuksista riippuen"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Voi soida tai väristä puhelimen asetuksista riippuen. Näistä keskusteluista (<xliff:g id="APP_NAME">%1$s</xliff:g>) syntyy oletuksena kuplia."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Voi soida tai väristä laitteen asetuksista riippuen"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Voi soida tai väristä laitteen asetuksista riippuen. Keskusteluista (<xliff:g id="APP_NAME">%1$s</xliff:g>) luodaan oletuksena kuplia."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Järjestelmä valitsee, kuuluuko tästä ilmoituksesta ääntä tai väriseekö se"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Tila:&lt;/b&gt; valittu oletusarvoiseksi"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Tila:&lt;/b&gt; hiljennetty"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Valitse sovellus lisätäksesi säätimiä"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# säädin lisätty.}other{# säädintä lisätty.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Poistettu"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Lisätty suosikkeihin"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Lisätty suosikkeihin sijalle <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Poistettu suosikeista"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="APP_LABEL">%2$s</xliff:g>)"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Kumoa"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Siirry lähemmäs, jotta <xliff:g id="DEVICENAME">%1$s</xliff:g> voi toistaa tämän"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Siirrä <xliff:g id="DEVICENAME">%1$s</xliff:g> lähemmäs toistaaksesi täällä"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Siirry lähemmäs laitetta (<xliff:g id="DEVICENAME">%1$s</xliff:g>) toistaaksesi täällä"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Toistetaan: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Jotain meni pieleen. Yritä uudelleen."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Latautuminen"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tabletti"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Epäaktiivinen, tarkista sovellus"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Ei löydy"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Ohjain ei ole käytettävissä"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Virhe, yritä uudelleen"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Lisää säätimiä"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Muokkaa säätimiä"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Lisää toistotapoja"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Ryhmä"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 laite valittu"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Äänenvoimakkuus"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Kaiuttimet ja näytöt"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ehdotetut laitteet"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Miten lähetys toimii"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Lähetys"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Lähistöllä olevat ihmiset, joilla on yhteensopiva Bluetooth-laite, voivat kuunnella lähettämääsi mediaa"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Tämä näyttö sammutetaan"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Taitettava laite taitetaan"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Taitettava laite käännetään ympäri"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akkua jäljellä <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Yhdistä näyttökynä laturiin"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Näyttökynän akku vähissä"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Tästä profiilista ei voi soittaa"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Työkäytäntö sallii sinun soittaa puheluita vain työprofiilista"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Vaihda työprofiiliin"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Sulje"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index be81a92..a65d2f3 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inférieure : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Les captures d\'écran du profil professionnel sont enregistrées dans l\'application <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Enregistreur d\'écran"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Trait. de l\'enregist. d\'écran…"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement d\'écran"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatique"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Aucun son ni vibration"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Aucun son ni vibration, et s\'affiche plus bas dans la section des conversations"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Peut sonner ou vibrer, selon les paramètres du téléphone"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Peut sonner ou vibrer, selon les paramètres du téléphone. Conversations des bulles de <xliff:g id="APP_NAME">%1$s</xliff:g> par défaut."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Peut sonner ou vibrer, selon les paramètres de l\'appareil"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Peut sonner ou vibrer, selon les paramètres de l\'appareil. Conversations des bulles de <xliff:g id="APP_NAME">%1$s</xliff:g> par défaut."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faire en sorte que le système détermine si cette notification devrait émettre un son ou vibrer"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;État :&lt;/b&gt; élevé à la catégorie Par défaut"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;État :&lt;/b&gt; abaissé à la catégorie Silencieux"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'application pour laquelle ajouter des commandes"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# commande ajoutée.}one{# commande ajoutée.}many{# de commandes ajoutées.}other{# commandes ajoutées.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Supprimé"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Ajouté aux favoris"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ajouté aux favoris, en position <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Supprimé des favoris"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Rapprochez-vous pour faire jouer le contenu sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Rapprochez-vous de <xliff:g id="DEVICENAME">%1$s</xliff:g> pour lire le contenu"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pour faire jouer le contenu ici, rapprochez-vous de <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_transfer_failed" msgid="7955354964610603723">"Un problème est survenu. Réessayez."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_failed" msgid="7955354964610603723">"Une erreur s\'est produite. Réessayez."</string>
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Chargement en cours…"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablette"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifiez l\'appli"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"La commande n\'est pas accessible"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Erreur. Veuillez réessayer."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ajouter des sorties"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Un appareil sélectionné"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Haut-parleurs et écrans"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Appareils suggérés"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement de la diffusion"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Diffusion"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les personnes à proximité disposant d\'appareils Bluetooth compatibles peuvent écouter le contenu multimédia que vous diffusez"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* Cet écran va s\'éteindre"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable en cours de dépliage"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable en train d\'être retourné"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Charge restante de la pile : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connectez votre stylet à un chargeur"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Pile du stylet faible"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Mode vidéo"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Impossible de passer un appel à partir de ce profil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Votre politique de l\'entreprise vous autorise à passer des appels téléphoniques uniquement à partir de votre profil professionnel"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Passer au profil professionnel"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fermer"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 8691663..c779b63 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inférieure : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Les captures d\'écran du profil professionnel sont enregistrées dans l\'appli <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Enregistreur d\'écran"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Enregistrement de l\'écran…"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement de l\'écran"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatique"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ni son, ni vibreur"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ni son, ni vibreur ; s\'affiche plus bas dans la section des conversations"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Son ou vibreur, selon les paramètres du téléphone"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Son ou vibreur, selon les paramètres du téléphone. Les conversations provenant de <xliff:g id="APP_NAME">%1$s</xliff:g> s\'affichent sous forme de bulles par défaut."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Peut sonner ou vibrer en fonction des paramètres de l\'appareil"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Peut sonner ou vibrer en fonction des paramètres de l\'appareil. Les conversations provenant de <xliff:g id="APP_NAME">%1$s</xliff:g> s\'affichent sous forme de bulles par défaut."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Laisser le système déterminer si cette notification doit être accompagnée d\'un son ou d\'une vibration"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;État :&lt;/b&gt; Élevée à la catégorie \"Par défaut\""</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;État :&lt;/b&gt; Abaissée à la catégorie \"Silencieux\""</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'appli pour laquelle ajouter des commandes"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# commande ajoutée.}one{# commande ajoutée.}many{# commandes ajoutées.}other{# commandes ajoutées.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Supprimé"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Ajouté aux favoris"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ajouté aux favoris, en position <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Supprimé des favoris"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> depuis <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Rapprochez-vous pour lire sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Rapprochez l\'appareil pour transférer la diffusion à votre <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Rapprochez-vous de votre <xliff:g id="DEVICENAME">%1$s</xliff:g> pour y lire le contenu"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pour lancer la lecture ici, rapprochez-vous de <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lecture sur <xliff:g id="DEVICENAME">%1$s</xliff:g>…"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Un problème est survenu. Réessayez."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Chargement…"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablette"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifier l\'appli"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Commande indisponible"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Erreur. Veuillez réessayer."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ajouter des sorties"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 appareil sélectionné"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Enceintes et écrans"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Appareils suggérés"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement des annonces"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Annonce"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les personnes à proximité équipées d\'appareils Bluetooth compatibles peuvent écouter le contenu multimédia que vous diffusez"</string>
@@ -1008,12 +1017,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Au moins un appareil est disponible"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Appuyez de manière prolongée sur raccourci"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Annuler"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Retourner maintenant"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Retourner"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Déplier le téléphone pour un meilleur selfie"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Passer à l\'écran frontal pour un meilleur selfie ?"</string>
-    <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Utilisez la caméra arrière pour prendre une photo plus large avec une résolution supérieure."</string>
-    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Cet écran sera désactivé"</b></string>
+    <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Utilisez la caméra arrière pour prendre une photo plus large d\'une résolution supérieure."</string>
+    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Cet écran s\'éteindra"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable qui est déplié"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable qui est retourné"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de batterie restante"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connectez votre stylet à un chargeur"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"La batterie du stylet est faible"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Caméra vidéo"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Impossible d\'appeler depuis ce profil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Votre règle professionnelle ne vous permet de passer des appels que depuis le profil professionnel"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Passer au profil professionnel"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fermer"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index f9738e3..9c270a9 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bordo inferior: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bordo esquerdo: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Bordo dereito: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"As capturas de pantalla do perfil de traballo gárdanse na aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ficheiros"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Gravadora da pantalla"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Procesando gravación pantalla"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación en curso sobre unha sesión de gravación de pantalla"</string>
@@ -383,7 +385,7 @@
     <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Cando compartes, gravas ou emites aplicacións, esta aplicación ten acceso a todo o que se vexa ou se reproduza nelas. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"O teu administrador de TI bloqueou esta aplicación"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A política do dispositivo desactivou a opción de capturar a pantalla"</string>
-    <string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string>
+    <string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Notificacións novas"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sen son nin vibración"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sen son nin vibración, e aparecen máis abaixo na sección de conversas"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"O teléfono pode soar ou vibrar en función da súa configuración"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Poderían facer que o teléfono soe ou vibre en función da súa configuración. Conversas desde a burbulla da aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> de forma predeterminada."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Poderían facer que o dispositivo soe ou vibre en función da súa configuración"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Poderían facer que o dispositivo soe ou vibre en función da súa configuración. As conversas de <xliff:g id="APP_NAME">%1$s</xliff:g> móstranse en burbullas de forma predeterminada."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Fai que o sistema determine se a notificación debe emitir un son ou unha vibración"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Estado:&lt;/b&gt; ascendeuse a Predeterminada"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Estado:&lt;/b&gt; o nivel diminuíuse a Silencioso"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Escolle unha aplicación para engadir controis"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Engadiuse # control.}other{Engadíronse # controis.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Quitouse"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Está entre os controis favoritos"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Está entre os controis favoritos (posición: <xliff:g id="NUMBER">%d</xliff:g>)"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Non está entre os controis favoritos"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Desfacer"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Achega o dispositivo para reproducir o contido en: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Achégate ao dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>) para reproducir o contido neste"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducir o contido aquí, achégate ao dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproducindo contido noutro dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Produciuse un erro. Téntao de novo."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Cargando"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tableta"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Comproba a app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Non se atopou"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"O control non está dispoñible"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Erro. Téntao de novo"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Engadir controis"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controis"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Engadir saídas"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Seleccionouse 1 dispositivo"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altofalantes e pantallas"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos suxeridos"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funcionan as difusións?"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Difusión"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As persoas que estean preto de ti e que dispoñan de dispositivos Bluetooth compatibles poden escoitar o contido multimedia que difundas"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Desactivarase esta pantalla"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pregable abríndose"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pregable xirando"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batería restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta o lapis óptico a un cargador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"O lapis óptico ten pouca batería"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videocámara"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Non se pode chamar desde este perfil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"A política do teu traballo só che permite facer chamadas de teléfono desde o perfil de traballo"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Cambiar ao perfil de traballo"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Pechar"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index dac1403..3457430 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"નીચેની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ડાબી બાજુની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"જમણી બાજુની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ઑફિસના સ્ક્રીનશૉટ <xliff:g id="APP">%1$s</xliff:g> ઍપમાં સાચવવામાં આવે છે"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ફાઇલો"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"સ્ક્રીન રેકોર્ડર"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"સ્ક્રીન રેકૉર્ડિંગ ચાલુ છે"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"સ્ક્રીન રેકોર્ડિંગ સત્ર માટે ચાલુ નોટિફિકેશન"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ઑટોમૅટિક રીતે"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"કોઈપણ સાઉન્ડ અથવા વાઇબ્રેશન નથી"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"કોઈપણ સાઉન્ડ અથવા વાઇબ્રેશન નથી અને વાતચીત વિભાગમાં તે વધુ નીચેની દિશાએ દેખાય છે"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ફોન સેટિંગના આધારે રિંગ અથવા વાઇબ્રેટ થઈ શકે છે"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ફોન સેટિંગના આધારે રિંગ અથવા વાઇબ્રેટ થઈ શકે છે. ડિફૉલ્ટ તરીકે <xliff:g id="APP_NAME">%1$s</xliff:g> બબલની વાતચીત."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"ડિવાઇસના સેટિંગના આધારે રિંગ અથવા વાઇબ્રેટ થઈ શકે છે"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ડિવાઇસના સેટિંગના આધારે રિંગ અથવા વાઇબ્રેટ થઈ શકે છે. ડિફૉલ્ટ તરીકે <xliff:g id="APP_NAME">%1$s</xliff:g> બબલની વાતચીત."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"આ નોટિફિકેશન સાઉન્ડ અથવા વાઇબ્રેટ કરી શકશે કે નહીં તે સિસ્ટમને નક્કી કરવા દો"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;સ્ટેટસ:&lt;/b&gt; ડિફૉલ્ટ તરીકે બઢતી આપવામાં આવી"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;સ્ટેટસ:&lt;/b&gt; સાઇલન્ટ પર અવનત કરવામાં આવ્યું"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"નિયંત્રણો ઉમેરવા માટે ઍપ પસંદ કરો"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# નિયંત્રણ ઉમેર્યું.}one{# નિયંત્રણ ઉમેર્યું.}other{# નિયંત્રણ ઉમેર્યા.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"કાઢી નાખ્યું"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> ઉમેરીએ?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"જ્યારે તમે <xliff:g id="APPNAME">%s</xliff:g> ઉમેરો, ત્યારે તે આ પૅનલમાં નિયંત્રણો અને કન્ટેન્ટ ઉમેરી શકે છે. કેટલીક ઍપમાં, અહીં કયા નિયંત્રણો દેખાય તે તમે પસંદ કરી શકો છો."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"મનપસંદમાં ઉમેર્યું"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"મનપસંદમાં ઉમેર્યું, સ્થાન <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"મનપસંદમાંથી કાઢી નાખ્યું"</string>
@@ -815,7 +819,7 @@
     <string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"અન્ય"</string>
     <string name="controls_dialog_title" msgid="2343565267424406202">"ડિવાઇસનાં નિયંત્રણોમાં ઉમેરો"</string>
     <string name="controls_dialog_ok" msgid="2770230012857881822">"ઉમેરો"</string>
-    <string name="controls_dialog_message" msgid="342066938390663844">"<xliff:g id="APP">%s</xliff:g> દ્વારા સૂચન કરેલા"</string>
+    <string name="controls_dialog_message" msgid="342066938390663844">"<xliff:g id="APP">%s</xliff:g> દ્વારા સૂચવેલા"</string>
     <string name="controls_tile_locked" msgid="731547768182831938">"ડિવાઇસ લૉક કરેલું છે"</string>
     <string name="controls_settings_show_controls_dialog_title" msgid="3357852503553809554">"લૉક સ્ક્રીનમાંથી ડિવાઇસ બતાવીએ અને નિયંત્રિત કરીએ?"</string>
     <string name="controls_settings_show_controls_dialog_message" msgid="7666211700524587969">"તમે તમારા બાહ્ય ડિવાઇસ માટેના નિયંત્રણો લૉક સ્ક્રીન પર ઉમેરી શકો છો.\n\nતમારી ડિવાઇસ ઍપ કદાચ તમને તમારો ફોન કે ટૅબ્લેટ અનલૉક કર્યા વિના અમુક ડિવાઇસ નિયંત્રિત કરવાની મંજૂરી આપી શકે.\n\nતમે ગમે ત્યારે સેટિંગમાં જઈને ફેરફાર કરી શકો છો."</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> પર <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"છેલ્લો ફેરફાર રદ કરો"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવા માટે વધુ નજીક ખસેડો"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"આમાં ચલાવવા માટે ડિવાઇસને <xliff:g id="DEVICENAME">%1$s</xliff:g>ની નજીક ખસેડો"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"અહીં ચલાવવા માટે, <xliff:g id="DEVICENAME">%1$s</xliff:g>ની નજીક લાવો"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવામાં આવી રહ્યું છે"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"કંઈક ખોટું થયું. ફરી પ્રયાસ કરો."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"લોડ થઈ રહ્યું છે"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ટૅબ્લેટ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"નિષ્ક્રિય, ઍપને ચેક કરો"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"મળ્યું નથી"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"નિયંત્રણ ઉપલબ્ધ નથી"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"ભૂલ, ફરીથી પ્રયાસ કરો"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"નિયંત્રણો ઉમેરો"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"નિયંત્રણોમાં ફેરફાર કરો"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ઍપ ઉમેરો"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"આઉટપુટ ઉમેરો"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ગ્રૂપ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ડિવાઇસ પસંદ કર્યું"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"વૉલ્યૂમ"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"સ્પીકર અને ડિસ્પ્લે"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"સૂચવેલા ડિવાઇસ"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"બ્રોડકાસ્ટ પ્રક્રિયાની કામ કરવાની રીત"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"બ્રોડકાસ્ટ કરો"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"સુસંગત બ્લૂટૂથ ડિવાઇસ ધરાવતા નજીકના લોકો તમે જે મીડિયા બ્રોડકાસ્ટ કરી રહ્યાં છો તે સાંભળી શકે છે"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ આ સ્ક્રીન બંધ થઈ જશે"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ અનફોલ્ડ કરવામાં આવી રહ્યું છે"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ ફ્લિપ કરવામાં આવી રહ્યું છે"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> બૅટરી બાકી છે"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"તમારા સ્ટાઇલસને ચાર્જર સાથે કનેક્ટ કરો"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"સ્ટાઇલસની બૅટરીમાં ચાર્જ ઓછો છે"</string>
+    <string name="video_camera" msgid="7654002575156149298">"વીડિયો કૅમેરા"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"આ પ્રોફાઇલ પરથી કૉલ કરી શકતા નથી"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"તમારી ઑફિસની પૉલિસી તમને માત્ર ઑફિસની પ્રોફાઇલ પરથી જ ફોન કૉલ કરવાની મંજૂરી આપે છે"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"ઑફિસની પ્રોફાઇલ પર સ્વિચ કરો"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"બંધ કરો"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 38d2bff..d96bc52 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"निचले किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"बाएं किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"दाएं किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"वर्क प्रोफ़ाइल से लिए गए स्क्रीनशॉट, <xliff:g id="APP">%1$s</xliff:g> ऐप्लिकेशन में सेव किए गए हैं"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"स्क्रीन रिकॉर्डर"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"स्क्रीन रिकॉर्डिंग को प्रोसेस किया जा रहा है"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रिकॉर्ड सेशन के लिए जारी सूचना"</string>
@@ -371,11 +373,11 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"इस फ़ंक्शन को उपलब्ध कराने वाली सेवा, रिकॉर्ड या कास्ट करते समय, आपकी स्क्रीन पर दिखने वाली या चलाई जाने वाली जानकारी को ऐक्सेस कर सकती है. इसमें पासवर्ड, पैसे चुकाने से जुड़ी जानकारी, फ़ोटो, मैसेज, और चलाए जाने वाले ऑडियो शामिल हैं."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"रिकॉर्डिंग या कास्ट करना शुरू करें?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> का इस्तेमाल करके रिकॉर्ड और कास्ट करना शुरू करें?"</string>
-    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> को शेयर या रिकॉर्ड करने की अनुमति दें?"</string>
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"क्या आपको शेयर या रिकॉर्ड करने की <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> को अनुमति देनी है?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"पूरी स्क्रीन"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"सिर्फ़ एक ऐप्लिकेशन"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी रखें"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ऐप्लिकेशन शेयर करें या उसकी रिकॉर्डिंग करें"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"क्या इस ऐप्लिकेशन को शेयर या रिकॉर्ड करने की अनुमति देनी है?"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"अपने-आप"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"किसी तरह की आवाज़ या वाइब्रेशन न हो"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"इससे किसी तरह की आवाज़ या वाइब्रेशन नहीं होता और बातचीत, सेक्शन में सबसे नीचे दिखती है"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"फ़ोन की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"फ़ोन की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है. <xliff:g id="APP_NAME">%1$s</xliff:g> में होने वाली बातचीत, डिफ़ॉल्ट रूप से बबल के तौर पर दिखती है."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"डिवाइस की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"डिवाइस की सेटिंग के आधार पर, सूचना आने पर घंटी बज सकती है या वाइब्रेशन हो सकता है. <xliff:g id="APP_NAME">%1$s</xliff:g> पर होने वाली बातचीत, डिफ़ॉल्ट रूप से बबल के तौर पर दिखती है."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"सिस्टम को यह तय करने की अनुमति दें कि इस सूचना के मिलने पर आवाज़ हो या वाइब्रेशन हो"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;स्थिति:&lt;/b&gt; लेवल बढ़ाकर, डिफ़ॉल्ट के तौर पर सेट किया गया"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;स्थिति:&lt;/b&gt; लेवल घटाकर, साइलेंट पर सेट किया गया"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"कंट्रोल जोड़ने के लिए ऐप्लिकेशन चुनें"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# कंट्रोल जोड़ा गया.}one{# कंट्रोल जोड़ा गया.}other{# कंट्रोल जोड़े गए.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"हटाया गया"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"पसंदीदा बनाया गया"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"पसंदीदा बनाया गया, क्रम संख्या <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"पसंदीदा से हटाया गया"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> पर, <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"पहले जैसा करें"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर मीडिया चलाने के लिए, अपने डिवाइस को उसके पास ले जाएं"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"अपने डिवाइस पर मीडिया फ़ाइल ट्रांसफ़र करने के लिए, उसे <xliff:g id="DEVICENAME">%1$s</xliff:g> के पास ले जाएं"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"मीडिया ट्रांसफ़र करने के लिए, <xliff:g id="DEVICENAME">%1$s</xliff:g> के करीब जाएं"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर मीडिया चल रहा है"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"कोई गड़बड़ी हुई. फिर से कोशिश करें."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"लोड हो रहा है"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"टैबलेट"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"काम नहीं कर रहा, ऐप जांचें"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"कंट्रोल नहीं है"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"कंट्रोल मौजूद नहीं है"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"गड़बड़ी हुई, फिर से कोशिश करें"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"कंट्राेल जोड़ें"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"कंट्रोल में बदलाव करें"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट जोड़ें"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ग्रुप"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिवाइस चुना गया"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"वॉल्यूम"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"स्पीकर और डिसप्ले"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"सुझाए गए डिवाइस"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ब्रॉडकास्ट करने की सुविधा कैसे काम करती है"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ब्रॉडकास्ट करें"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"आपके आस-पास मौजूद लोग, ब्रॉडकास्ट किए जा रहे मीडिया को सुन सकते हैं. हालांकि, इसके लिए उनके पास ऐसे ब्लूटूथ डिवाइस होने चाहिए जिन पर मीडिया चलाया जा सके"</string>
@@ -1008,12 +1017,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• कम से कम एक डिवाइस उपलब्ध है"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"शॉर्टकट को दबाकर रखें"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"रद्द करें"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"कैमरा अभी स्विच करें"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"अभी स्विच करें"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"बेहतर सेल्फ़ी के लिए फ़ोन को अनफ़ोल्ड करें"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"बेहतर सेल्फ़ी के लिए फ़्रंट डिसप्ले पर स्विच करें?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"वाइड ऐंगल में हाई रिज़ॉल्यूशन वाली फ़ोटो लेने के लिए, पीछे का कैमरा इस्तेमाल करें."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ यह स्क्रीन बंद हो जाएगी"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फ़ोल्ड किया जा सकने वाला डिवाइस अनफ़ोल्ड किया जा रहा है"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फ़ोल्ड किया जा सकने वाला डिवाइस पलटा जा रहा है"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> बैटरी बची है"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"अपने स्टाइलस को चार्ज करें"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"स्टाइलस की बैटरी कम है"</string>
+    <string name="video_camera" msgid="7654002575156149298">"वीडियो कैमरा"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"यह प्रोफ़ाइल होने पर कॉल नहीं की जा सकती"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"ऑफ़िस की नीति के तहत, वर्क प्रोफ़ाइल होने पर ही फ़ोन कॉल किए जा सकते हैं"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"वर्क प्रोफ़ाइल पर स्विच करें"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"बंद करें"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 7613317..cb83de9 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Donji rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Lijevi rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desni rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Snimke zaslona s poslovnog profila spremaju se u aplikaciju <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Datoteke"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Snimač zaslona"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrada snimanja zaslona"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Tekuća obavijest za sesiju snimanja zaslona"</string>
@@ -377,7 +379,7 @@
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Kad dijelite, snimate ili emitirate, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kad dijelite, snimate ili emitirate aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
-    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijeljenje ili snimanje pomoću aplikacije"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijeljenje ili snimanje aplikacije"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Želite li ovoj aplikaciji omogućiti dijeljenje ili bilježenje?"</string>
     <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kad dijelite, snimate ili emitirate, ova aplikacija ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
     <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kad dijelite, snimate ili emitirate aplikaciju, ova aplikacija ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatski"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Bez zvuka ili vibracije"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Bez zvuka ili vibracije i prikazuje se pri dnu odjeljka razgovora"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Možda će zvoniti ili vibrirati, ovisno o postavkama telefona"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Možda će zvoniti ili vibrirati, ovisno o postavkama telefona. Razgovori iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> prikazuju se u oblačiću prema zadanim postavkama."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Možda će zvoniti ili vibrirati, ovisno o postavkama uređaja"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Možda će zvoniti ili vibrirati, ovisno o postavkama uređaja. Razgovori iz aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g> prikazuju se u oblačiću prema zadanim postavkama."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Neka sustav odredi treba li obavijest najaviti zvukom ili vibracijom"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; promaknuta u zadanu"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; prebačena u bešumnu"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Odabir aplikacije za dodavanje kontrola"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Dodana je # kontrola.}one{Dodana je # kontrola.}few{Dodane su # kontrole.}other{Dodano je # kontrola.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Uklonjeno"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Želite li dodati aplikaciju <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Kada dodate aplikaciju <xliff:g id="APPNAME">%s</xliff:g>, može dodati kontrole i sadržaj na ovu ploču. U nekim aplikacijama možete odabrati koje se kontrole prikazuju ovdje."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano u favorite"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano u favorite, položaj <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Uklonjeno iz favorita"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite se radi reprodukcije na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g> da biste na njemu reproducirali"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Da biste reproducirali ovdje, približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproducira se na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Nešto nije u redu. Pokušajte ponovo."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Učitavanje"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, provjerite aplik."</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Pogreška, pokušajte ponovo"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj kontrole"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Uredi kontrole"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Dodavanje aplikacije"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodavanje izlaza"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Odabran je jedan uređaj"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Glasnoća"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvučnici i zasloni"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predloženi uređaji"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako emitiranje funkcionira"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Emitiranje"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osobe u blizini s kompatibilnim Bluetooth uređajima mogu slušati medije koje emitirate"</string>
@@ -1008,12 +1014,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Dostupan je najmanje jedan uređaj"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Prečac za dodirnuti i zadržati"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Odustani"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Okreni odmah"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Prebaci"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Otvorite telefon da biste snimili bolji selfie"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Prebaciti na prednji zaslon za bolji selfie?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Upotrijebite stražnji fotoaparat za širu fotografiju s višom razlučivošću."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ovaj će se zaslon isključiti"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rasklopljen sklopivi uređaj"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Okretanje sklopivog uređaja sa svih strana"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo je <xliff:g id="PERCENTAGE">%s</xliff:g> baterije"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Priključite pisaljku na punjač"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Slaba baterija pisaljke"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nije moguće uspostavljati pozive s ovog profila"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Vaša pravila za poslovne uređaje omogućuju vam upućivanje poziva samo s poslovnog profila"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Prijeđite na poslovni profil"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zatvori"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index c2b94c0..cabd898 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alsó rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bal oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Jobb oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"A munkahelyi képernyőképeket a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásba menti a rendszer"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fájlok"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Képernyőrögzítő"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Képernyőrögzítés feldolgozása"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Folyamatban lévő értesítés képernyőrögzítési munkamenethez"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatikus"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Nincs hang és rezgés"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Nincs hang és rezgés, továbbá lejjebb jelenik meg a beszélgetések szakaszában"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"A telefonbeállítások alapján csöröghet és rezeghet"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"A telefonbeállítások alapján csöröghet és rezeghet. A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazásban lévő beszélgetések alapértelmezés szerint buborékban jelennek meg."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Az eszközbeállítások alapján csöröghet és rezeghet"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Az eszközbeállítások alapján csöröghet és rezeghet. A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazásban lévő beszélgetések alapértelmezés szerint buborékban jelennek meg."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"A rendszer határozza meg, hogy ez az értesítés adjon-e ki hangot, illetve rezegjen-e"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Állapot:&lt;/b&gt; alapértelmezettre állítva"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Állapot:&lt;/b&gt; némára állítva"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Válasszon alkalmazást a vezérlők hozzáadásához"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# vezérlő hozzáadva.}other{# vezérlő hozzáadva.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Eltávolítva"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Hozzáadja a(z) <xliff:g id="APPNAME">%s</xliff:g> alkalmazást?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"A(z) <xliff:g id="APPNAME">%s</xliff:g> hozzáadását követően az alkalmazás vezérlőelemeket és tartalmakat adhat hozzá ehhez a panelhez. Egyes alkalmazásokban kiválasztható, hogy mely vezérlőelemek jelenjenek meg itt."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Hozzáadva a kedvencekhez"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Hozzáadva a kedvencekhez <xliff:g id="NUMBER">%d</xliff:g>. helyen"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Eltávolítva a kedvencek közül"</string>
@@ -849,12 +853,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című számának lejátszása innen: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> lejátszása innen: <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Visszavonás"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Menjen közelebb a következőn való lejátszáshoz: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Menjen közelebb a(z) <xliff:g id="DEVICENAME">%1$s</xliff:g> eszközhöz, hogy itt játszhassa le a tartalmat"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Menjen közelebb, ha itt szeretné lejátszani: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ha szeretné itt lejátszani, helyezkedjen közelebb a következőhöz: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Lejátszás folyamatban a(z) <xliff:g id="DEVICENAME">%1$s</xliff:g> eszközön"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Hiba történt. Próbálkozzon újra."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Betöltés…"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"táblagép"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktív, ellenőrizze az appot"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nem található"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Nem hozzáférhető vezérlő"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Hiba történt. Próbálja újra."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Vezérlők hozzáadása"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Vezérlők szerkesztése"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Alkalmazás hozzáadása"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Kimenetek hozzáadása"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Csoport"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 eszköz kiválasztva"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hangerő"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hangfalak és kijelzők"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Javasolt eszközök"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"A közvetítés működése"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Közvetítés"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"A közelben tartózkodó, kompatibilis Bluetooth-eszközzel rendelkező személyek meghallgathatják az Ön közvetített médiatartalmait"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ A képernyő kikapcsol"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Összehajtható eszköz kihajtása"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Összehajtható eszköz körbeforgatása"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akkumulátor töltöttségi szintje: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Tegye töltőre az érintőceruzát"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Az érintőceruza töltöttsége alacsony"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nem lehet hívást kezdeményezni ebből a profilból"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"A munkahelyi házirend csak munkaprofilból kezdeményezett telefonhívásokat engedélyez"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Váltás munkaprofilra"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Bezárás"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 5623ecf..5b3cefb 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ներքևի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Ձախ կողմի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Աջ կողմի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Աշխատանքային սքրինշոթները պահվում են «<xliff:g id="APP">%1$s</xliff:g>» հավելվածում"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ֆայլեր"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Էկրանի տեսագրիչ"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Էկրանի տեսագրության մշակում"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Էկրանի տեսագրման աշխատաշրջանի ընթացիկ ծանուցում"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Ավտոմատ"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Առանց ձայնի կամ թրթռոցի"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Առանց ձայնի և թրթռոցի, հայտնվում է զրույցների ցանկի ներքևում"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Կարող է զնգալ կամ թրթռալ (հեռախոսի կարգավորումներից կախված)"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Կարող է զնգալ կամ թրթռալ (հեռախոսի կարգավորումներից կախված)։ <xliff:g id="APP_NAME">%1$s</xliff:g>-ի զրույցներն ըստ կանխադրման հայտնվում են ամպիկների տեսքով։"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Կարող է զնգալ կամ թրթռալ՝ կախված սարքի կարգավորումներից"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Կարող է զնգալ կամ թրթռալ՝ կախված սարքի կարգավորումներից։ <xliff:g id="APP_NAME">%1$s</xliff:g>-ի զրույցներն ըստ կանխադրման հայտնվում են ամպիկների տեսքով։"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Թող համակարգն ավտոմատ որոշի՝ արդյոք այս ծանուցումը ձայնով, թե թրթռոցով է պետք մատուցել"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Կարգավիճակը․&lt;/b&gt; բարձրացվել է և դարձել կանխադրված"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Կարգավիճակը․&lt;/b&gt; իջեցվել է և դարձել անձայն"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Ընտրեք հավելված` կառավարման տարրեր ավելացնելու համար"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Ավելացվեց կառավարման # տարր։}one{Ավելացվեց կառավարման # տարր։}other{Ավելացվեց կառավարման # տարր։}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Հեռացված է"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Ավելացված է ընտրանիում"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ավելացված է ընտրանիում, դիրքը՝ <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Հեռացված է ընտրանուց"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="APP_LABEL">%2$s</xliff:g> հավելվածից"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Հետարկել"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ավելի մոտ եկեք՝ <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքում նվագարկելու համար"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Ավելի մոտեցեք «<xliff:g id="DEVICENAME">%1$s</xliff:g>» սարքին՝ նվագարկումը սկսելու համար"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Այստեղ նվագարկելու համար մոտեցեք <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքին"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Նվագարկվում է «<xliff:g id="DEVICENAME">%1$s</xliff:g>» սարքում"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Սխալ առաջացավ։ Նորից փորձեք։"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Բեռնվում է"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"պլանշետ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Ակտիվ չէ, ստուգեք հավելվածը"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Չի գտնվել"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Կառավարման տարրը հասանելի չէ"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Սխալ առաջացավ։ Նորից փորձեք։"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Ավելացնել կառավարման տարրեր"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Փոփոխել կառավարման տարրերը"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ավելացրեք մուտքագրման սարքեր"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Խումբ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ընտրված է 1 սարք"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ձայնի ուժգնություն"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Բարձրախոսներ և էկրաններ"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Առաջարկվող սարքեր"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Ինչպես է աշխատում հեռարձակումը"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Հեռարձակում"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ձեր մոտակայքում գտնվող՝ համատեղելի Bluetooth սարքերով մարդիկ կարող են լսել մեդիա ֆայլերը, որոնք դուք հեռարձակում եք։"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Այս էկրանը կանջատվի"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ծալովի սարք՝ բացված վիճակում"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Ծալովի սարք՝ շրջված վիճակում"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Մարտկոցի լիցքը՝ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ձեր ստիլուսը միացրեք լիցքավորիչի"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Ստիլուսի մարտկոցի լիցքի ցածր մակարդակ"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Տեսախցիկ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Հնարավոր չէ զանգել այս պրոֆիլից"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Ձեր աշխատանքային կանոնների համաձայն՝ դուք կարող եք զանգեր կատարել աշխատանքային պրոֆիլից"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Անցնել աշխատանքային պրոֆիլ"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Փակել"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index bf5f8b0..a00839f 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Batas bawah <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Batas kiri <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Batas kanan <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Screenshot dengan profil kerja disimpan di aplikasi <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Perekam Layar"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Memproses perekaman layar"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifikasi yang sedang berjalan untuk sesi rekaman layar"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Otomatis"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Tidak ada suara atau getaran"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Tidak ada suara atau getaran dan ditampilkan lebih rendah di bagian percakapan"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Dapat berdering atau bergetar berdasarkan setelan ponsel"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Dapat berdering atau bergetar berdasarkan setelan ponsel. Percakapan dari balon <xliff:g id="APP_NAME">%1$s</xliff:g> secara default."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Dapat berdering atau bergetar berdasarkan setelan perangkat"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Dapat berdering atau bergetar berdasarkan setelan perangkat. Percakapan <xliff:g id="APP_NAME">%1$s</xliff:g> ditampilkan sebagai balon secara default."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Biarkan sistem menentukan apakah notifikasi ini akan berbunyi atau bergetar"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Dipromosikan menjadi Default"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Didemosikan menjadi Senyap"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Pilih aplikasi untuk menambahkan kontrol"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrol ditambahkan.}other{# kontrol ditambahkan.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Dihapus"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Difavoritkan"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Difavoritkan, posisi <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Batal difavoritkan"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> dari <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Urungkan"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Dekatkan untuk memutar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Dekatkan ke <xliff:g id="DEVICENAME">%1$s</xliff:g> untuk memutar di sini"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Untuk memutar di sini, dekatkan ke <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Diputar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Terjadi error. Coba lagi."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Memuat"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Nonaktif, periksa aplikasi"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol tidak tersedia"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Error, coba lagi"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Tambahkan kontrol"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit kontrol"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tambahkan output"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 perangkat dipilih"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speaker &amp; Layar"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Perangkat yang Disarankan"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cara kerja siaran"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Siaran"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Orang di dekat Anda dengan perangkat Bluetooth yang kompatibel dapat mendengarkan media yang sedang Anda siarkan"</string>
@@ -1010,10 +1019,19 @@
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Batal"</string>
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Balik sekarang"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Bentangkan ponsel untuk selfie yang lebih baik"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Gunakan layar depan untuk selfie yang lebih baik?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Balik ke layar depan untuk selfie yang lebih bagus?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Gunakan kamera belakang untuk foto dengan resolusi lebih tinggi dan lebih lebar."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Layar ini akan dinonaktifkan"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Perangkat foldable sedang dibentangkan"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Perangkat foldable sedang dibalik"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Baterai tersisa <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Hubungkan stilus ke pengisi daya"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Baterai stilus lemah"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Kamera video"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Tidak dapat melakukan panggilan dari profil ini"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Kebijakan kantor mengizinkan Anda melakukan panggilan telepon hanya dari profil kerja"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Beralih ke profil kerja"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Tutup"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index f3efb86..319988c 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Neðri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vinstri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Hægri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Vinnuskjámyndir eru vistaðar í forritinu <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Skrár"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Skjáupptaka"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Vinnur úr skjáupptöku"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Áframhaldandi tilkynning fyrir skjáupptökulotu"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Sjálfvirk"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ekkert hljóð eða titringur"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ekkert hljóð eða titringur og birtist neðar í samtalshluta"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Gæti hringt eða titrað eftir stillingum símans"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Gæti hringt eða titrað eftir stillingum símans. Samtöl á <xliff:g id="APP_NAME">%1$s</xliff:g> birtast sjálfkrafa í blöðru."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Gæti hringt eða titrað en það fer eftir stillingum tækisins"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Gæti hringt eða titrað en það fer eftir stillingum tækisins. Samtöl frá <xliff:g id="APP_NAME">%1$s</xliff:g> birtast sjálfkrafa í blöðru."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Láta kerfið ákvarða hvort hljóð eða titringur fylgir þessari tilkynningu"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Staða:&lt;/b&gt; gerð sjálfgefin"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Staða:&lt;/b&gt; var gerð þögul"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Veldu forrit til að bæta við stýringum"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# stýringu bætt við.}one{# stýringu bætt við.}other{# stýringum bætt við.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Fjarlægt"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Eftirlæti"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Eftirlæti, staða <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Fjarlægt úr eftirlæti"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> í <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Afturkalla"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Færðu nær til að spila í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Færðu tækið nær <xliff:g id="DEVICENAME">%1$s</xliff:g> til að spila hér"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Farðu nær <xliff:g id="DEVICENAME">%1$s</xliff:g> til að spila hér"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Í spilun í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Eitthvað fór úrskeiðis. Reyndu aftur."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Hleður"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"spjaldtölva"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Óvirkt, athugaðu forrit"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Fannst ekki"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Stýring er ekki tiltæk"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Villa, reyndu aftur"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Bæta við stýringum"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Breyta stýringum"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Bæta við úttaki"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Hópur"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 tæki valið"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hljóðstyrkur"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hátalarar og skjáir"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Tillögur að tækjum"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Svona virkar útsending"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Útsending"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Fólk nálægt þér með samhæf Bluetooth-tæki getur hlustað á efnið sem þú sendir út"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Slökkt verður á þessum skjá"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Samanbrjótanlegt tæki opnað"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Samanbrjótanlegu tæki snúið við"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> hleðsla eftir á rafhlöðu"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Tengdu pennann við hleðslutæki"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Rafhlaða pennans er að tæmast"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Kvikmyndatökuvél"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ekki er hægt að hringja úr þessu sniði"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Vinnureglur gera þér aðeins kleift að hringja símtöl úr vinnusniði"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Skipta yfir í vinnusnið"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Loka"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index e8c6a5b..fffb8d6 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inferiore, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite sinistro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite destro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Gli screenshot acquisiti nel profilo di lavoro sono salvati nell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Registrazione dello schermo"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Elaboraz. registraz. schermo"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifica costante per una sessione di registrazione dello schermo"</string>
@@ -371,7 +373,7 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Il servizio che offre questa funzione avrà accesso a tutte le informazioni visibili sul tuo schermo o riprodotte dal tuo dispositivo durante la registrazione o la trasmissione. Sono incluse informazioni quali password, dettagli sui pagamenti, foto, messaggi e audio riprodotto."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Vuoi avviare la registrazione o la trasmissione?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Vuoi avviare la registrazione o la trasmissione con <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
-    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Consenti a <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> di condividere o registrare?"</string>
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Consentire a <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> di condividere o registrare?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Schermo intero"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Una sola app"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Quando condividi, registri o trasmetti, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatico"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Nessun suono o vibrazione"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Nessun suono o vibrazione e appare più in basso nella sezione delle conversazioni"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Può suonare o vibrare in base alle impostazioni del telefono"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Può suonare o vibrare in base alle impostazioni del telefono. Le conversazioni di <xliff:g id="APP_NAME">%1$s</xliff:g> appaiono come bolla per impostazione predefinita."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Potrebbero essere attivati lo squillo o la vibrazione in base alle impostazioni del dispositivo"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Potrebbero essere attivati lo squillo o la vibrazione in base alle impostazioni del dispositivo. Conversazioni dalla bolla <xliff:g id="APP_NAME">%1$s</xliff:g> per impostaz. predefinita."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Fai stabilire al sistema se questa notifica deve emettere suoni o vibrazioni"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Stato:&lt;/b&gt; promossa a Predefinita"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Stato:&lt;/b&gt; retrocessa a Silenziosa"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Scegli un\'app per aggiungere controlli"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# controllo aggiunto.}many{# controlli aggiunti.}other{# controlli aggiunti.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Rimosso"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Vuoi aggiungere <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Se la aggiungi, l\'app <xliff:g id="APPNAME">%s</xliff:g> può aggiungere controlli e contenuti a questo riquadro. In alcune app puoi scegliere quali controlli visualizzare qui."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Aggiunto ai preferiti"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Preferito, posizione <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Rimosso dai preferiti"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> da <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Annulla"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Avvicinati per riprodurre su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Avvicinati a <xliff:g id="DEVICENAME">%1$s</xliff:g> per riprodurre i contenuti qui"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Per riprodurre qui i contenuti, avvicinati a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"In riproduzione su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Si è verificato un errore. Riprova."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Caricamento in corso…"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inattivo, controlla l\'app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Controllo non trovato"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Il controllo non è disponibile"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Errore, riprova"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Aggiungi controlli"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Modifica controlli"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Aggiungi app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Aggiungi uscite"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppo"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selezionato"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speaker e display"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivi consigliati"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Come funziona la trasmissione"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Annuncio"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Le persone vicine a te che hanno dispositivi Bluetooth compatibili possono ascoltare i contenuti multimediali che stai trasmettendo"</string>
@@ -1010,10 +1016,19 @@
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Annulla"</string>
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Gira ora"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Apri il telefono per un selfie migliore"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Girare su display frontale per un selfie migliore?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Passare al display frontale per un selfie migliore?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Utilizza la fotocamera posteriore per una foto più ampia con maggiore risoluzione."</string>
-    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Questo schermo verrà disattivato"</b></string>
+    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Questo schermo verrà spento"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pieghevole che viene aperto"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pieghevole che viene capovolto"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteria rimanente"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connetti lo stilo a un caricabatterie"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Batteria stilo in esaurimento"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videocamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Impossibile chiamare da questo profilo"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Le norme di lavoro ti consentono di fare telefonate soltanto dal profilo di lavoro"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Passa a profilo di lavoro"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Chiudi"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 690b6b2..dfe8a64 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> אחוז מהשוליים התחתונים"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> אחוז מהשוליים השמאליים"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> אחוז מהשוליים הימניים"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"צילומי מסך בפרופיל העבודה נשמרים באפליקציה <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"קבצים"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"מקליט המסך"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"מתבצע עיבוד של הקלטת מסך"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"התראה מתמשכת לסשן הקלטת מסך"</string>
@@ -98,8 +100,8 @@
     <string name="screenrecord_description" msgid="1123231719680353736">"‏בזמן ההקלטה, מערכת Android יכולה לתעד מידע רגיש שגלוי במסך או מופעל במכשיר שלך. מידע זה כולל סיסמאות, פרטי תשלום, תמונות, הודעות ואודיו."</string>
     <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"הקלטה של כל המסך"</string>
     <string name="screenrecord_option_single_app" msgid="5954863081500035825">"הקלטה של אפליקציה אחת"</string>
-    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"‏בזמן ההקלטה, תהיה ל-Android גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
-    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"‏בזמן הקלטה של אפליקציה, תהיה ל-Android גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"‏בזמן ההקלטה, תהיה ל-Android גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. חשוב להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+    <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"‏בזמן הקלטה של אפליקציה, תהיה ל-Android גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. חשוב להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
     <string name="screenrecord_start_recording" msgid="348286842544768740">"התחלת ההקלטה"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"הקלטת אודיו"</string>
     <string name="screenrecord_device_audio_label" msgid="9016927171280567791">"אודיו מהמכשיר"</string>
@@ -374,8 +376,8 @@
     <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"לאפשר לאפליקציה <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> לשתף או להקליט?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"כל המסך"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"אפליקציה אחת"</string>
-    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"‏בזמן שיתוף, הקלטה או העברה (cast) תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"‏בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"‏בזמן שיתוף, הקלטה או העברה (cast) תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. חשוב להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"‏בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. חשוב להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"המשך"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"שיתוף או הקלטה של אפליקציה"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"לאפשר לאפליקציה הזו לשתף או להקליט?"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"באופן אוטומטי"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"ללא צליל או רטט"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ללא צליל או רטט ומופיעה למטה בקטע התראות השיחה"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ייתכן שיופעל צלצול או רטט בהתאם להגדרות הטלפון"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ייתכן שיופעל צלצול או רטט בהתאם להגדרות הטלפון. שיחות מהאפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מופיעות בבועות כברירת מחדל."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"ייתכן שיופעל צלצול או רטט בהתאם להגדרות במכשיר"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ייתכן שיופעל צלצול או רטט בהתאם להגדרות במכשיר. שיחות מהאפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מופיעות בבועות כברירת מחדל."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"אפשר לתת למערכת לקבוע אם ההתראה הזאת צריכה להיות מלווה בצליל או ברטט"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"‏&lt;b&gt;הסטטוס:&lt;/b&gt; הועלה בדרגה ל\'ברירת מחדל\'"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"‏&lt;b&gt;הסטטוס:&lt;/b&gt; הורד בדרגה ל\'שקט\'"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"יש לבחור אפליקציה כדי להוסיף פקדים"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{נוסף אמצעי בקרה אחד (#).}one{נוספו # אמצעי בקרה.}two{נוספו # אמצעי בקרה.}other{נוספו # אמצעי בקרה.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"הוסר"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"סומן כמועדף"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"סומן כמועדף, במיקום <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"הוסר מהמועדפים"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> מ-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"ביטול"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"צריך להתקרב כדי להפעיל מוזיקה במכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"צריך להתקרב אל <xliff:g id="DEVICENAME">%1$s</xliff:g> כדי להפעיל כאן"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"צריך להתקרב כדי להפעיל מדיה במכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"כדי להפעיל במכשיר הזה, יש להתקרב אל <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"פועלת ב-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"משהו השתבש. יש לנסות שוב."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"בטעינה"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"טאבלט"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"לא פעיל, יש לבדוק את האפליקציה"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"לא נמצא"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"הפקד לא זמין"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"שגיאה, יש לנסות שוב"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"הוספת פקדים"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"עריכת פקדים"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"הוספת מכשירי פלט"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"קבוצה"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"נבחר מכשיר אחד"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"עוצמת הקול"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"‎<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%‎‎"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"רמקולים ומסכים"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"הצעות למכשירים"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"הסבר על שידורים"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"שידור"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"‏אנשים בקרבת מקום עם מכשירי Bluetooth תואמים יכולים להאזין למדיה שמשודרת על ידך"</string>
@@ -1008,12 +1017,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• יש לפחות מכשיר אחד זמין"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"מקש קיצור ללחיצה ארוכה"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"ביטול"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"הפכת את המכשיר"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"אני רוצה להפוך"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"כדי לצלם תמונת סלפי טובה יותר, פותחים את הטלפון"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"להפוך למסך הקדמי כדי לצלם תמונת סלפי טובה יותר?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"במצלמה האחורית אפשר לצלם תמונה רחבה יותר ברזולוציה גבוהה יותר."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ המסך יכבה"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"מכשיר מתקפל עובר למצב לא מקופל"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"מכשיר מתקפל עובר למצב מהופך"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"רמת הטעינה שנותרה בסוללה: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"כדאי לחבר את הסטיילוס למטען"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"הסוללה של הסטיילוס חלשה"</string>
+    <string name="video_camera" msgid="7654002575156149298">"מצלמת וידאו"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"אי אפשר להתקשר מהפרופיל הזה"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"המדיניות של מקום העבודה מאפשרת לך לבצע שיחות טלפון רק מפרופיל העבודה"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"מעבר לפרופיל עבודה"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"סגירה"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 5bd580c..7db687a 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"下部の境界線 <xliff:g id="PERCENT">%1$d</xliff:g> パーセント"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左の境界線 <xliff:g id="PERCENT">%1$d</xliff:g> パーセント"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右の境界線 <xliff:g id="PERCENT">%1$d</xliff:g> パーセント"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"仕事用のスクリーンショットは <xliff:g id="APP">%1$s</xliff:g> アプリに保存されます"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ファイル"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"スクリーン レコーダー"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"画面の録画を処理しています"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"画面の録画セッション中の通知"</string>
@@ -445,7 +447,7 @@
     <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"字幕のオーバーレイ"</string>
     <string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"有効にする"</string>
     <string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"無効にする"</string>
-    <string name="sound_settings" msgid="8874581353127418308">"サウンドとバイブレーション"</string>
+    <string name="sound_settings" msgid="8874581353127418308">"音とバイブレーション"</string>
     <string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"設定"</string>
     <string name="screen_pinning_title" msgid="9058007390337841305">"アプリは固定されています"</string>
     <string name="screen_pinning_description" msgid="8699395373875667743">"固定を解除するまで画面が常に表示されるようになります。[戻る] と [最近] を同時に押し続けると固定が解除されます。"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"自動"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"着信音もバイブレーションも無効になります"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"着信音もバイブレーションも無効になり会話セクションの下に表示されます"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"スマートフォンの設定を基に着信音またはバイブレーションが有効になります"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"スマートフォンの設定を基に着信音またはバイブレーションが有効になります。デフォルトでは <xliff:g id="APP_NAME">%1$s</xliff:g> からの会話がバブルとして表示されます。"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"デバイスの設定を基に着信音またはバイブレーションが有効になります"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"デバイスの設定を基に着信音またはバイブレーションが有効になります。デフォルトでは <xliff:g id="APP_NAME">%1$s</xliff:g> からの会話がふきだしで表示されます。"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"この通知を音またはバイブレーションで知らせるかどうかの自動判断"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;ステータス:&lt;/b&gt; ランクがデフォルトに上がりました"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;ステータス:&lt;/b&gt; ランクがサイレントに下がりました"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"コントロールを追加するアプリの選択"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# 件のコントロールを追加しました。}other{# 件のコントロールを追加しました。}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"削除済み"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> を追加しますか?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> を追加することで、コントロールやコンテンツをこのパネルに追加できます。一部のアプリでは、ここに表示されるコントロールを選択できます。"</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"お気に入りに追加済み"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"お気に入りに追加済み、位置: <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"お気に入りから削除済み"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> を <xliff:g id="APP_LABEL">%2$s</xliff:g> で再生"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"元に戻す"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生するにはもっと近づけてください"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ここで再生するには<xliff:g id="DEVICENAME">%1$s</xliff:g>に近づいてください"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"このデバイスで再生するには、<xliff:g id="DEVICENAME">%1$s</xliff:g> にもっと近づけてください"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生しています"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"エラーが発生しました。もう一度お試しください。"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"読み込んでいます"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"タブレット"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"無効: アプリをご確認ください"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"見つかりませんでした"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"コントロールを使用できません"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"エラー: もう一度お試しください"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"コントロールを追加"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"コントロールを編集"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"アプリを追加"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"出力の追加"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"グループ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"選択したデバイス: 1 台"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"音量"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"スピーカーとディスプレイ"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"デバイスの候補"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ブロードキャストの仕組み"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ブロードキャスト"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Bluetooth 対応デバイスを持っている付近のユーザーは、あなたがブロードキャストしているメディアを聴けます"</string>
@@ -1008,12 +1014,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• 利用できるデバイスが 1 台以上ある"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"ショートカットの長押しが必要です"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"キャンセル"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"切り替えましょう"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"切り替える"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"高画質で撮るにはスマートフォンを開いてください"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"前面ディスプレイに切り替えて綺麗に撮りましょう"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"高解像度で広い範囲を撮影するには、背面カメラを使用してください。"</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱この画面は OFF になります"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"折りたたみ式デバイスが広げられている"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"折りたたみ式デバイスがひっくり返されている"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"バッテリー残量 <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"タッチペンを充電器に接続してください"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"タッチペンのバッテリー残量が少なくなっています"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ビデオカメラ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"このプロファイルからは通話を発信できません"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"仕事用ポリシーでは、通話の発信を仕事用プロファイルからのみに制限できます"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"仕事用プロファイルに切り替える"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"閉じる"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index d25daf4..f4c934c 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ქვედა ზღვარი: <xliff:g id="PERCENT">%1$d</xliff:g> პროცენტი"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"მარცხენა ზღვარი: <xliff:g id="PERCENT">%1$d</xliff:g> პროცენტი"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"მარჯვენა ზღვარი: <xliff:g id="PERCENT">%1$d</xliff:g> პროცენტი"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"სამუშაო ეკრანის ანაბეჭდები ინახება <xliff:g id="APP">%1$s</xliff:g> აპში"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ფაილები"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"ეკრანის ჩამწერი"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ეკრანის ჩანაწერი მუშავდება"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"უწყვეტი შეტყობინება ეკრანის ჩაწერის სესიისთვის"</string>
@@ -98,7 +100,7 @@
     <string name="screenrecord_description" msgid="1123231719680353736">"ჩაწერის განმავლობაში Android სისტემას შეუძლია აღბეჭდოს ნებისმიერი სენსიტიური ინფორმაცია, რომელიც თქვენს ეკრანზე გამოჩნდება ან თქვენს მოწყობილობაზე დაიკვრება. აღნიშნული მოიცავს პაროლებს, გადახდის დეტალებს, ფოტოებს, შეტყობინებებსა და აუდიოს."</string>
     <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"მთელი ეკრანის ჩაწერა"</string>
     <string name="screenrecord_option_single_app" msgid="5954863081500035825">"ერთი აპის ჩაწერა"</string>
-    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"სანამ აპის ჩაწერას ახორციელებთ, Android-ს აქვს წვდომა ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენი მოწყობილობის მეშვეობით. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან."</string>
+    <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"ჩაწერის განხორციელებისას Android-ს აქვს წვდომა ყველაფერზე, რაც თქვენს ეკრანზე ჩანს ან უკრავს თქვენი მოწყობილობის მეშვეობით. შესაბამისად, გამოიჩინეთ სიფრთხილე პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან თუ სხვა მგრძნობიარე ინფორმაციასთან დაკავშირებით."</string>
     <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"სანამ აპის ჩაწერას ახორციელებთ, Android-ს აქვს წვდომა ყველაფერზე, რაც ჩანს აპში ან ითამაშეთ. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან"</string>
     <string name="screenrecord_start_recording" msgid="348286842544768740">"ჩაწერის დაწყება"</string>
     <string name="screenrecord_audio_label" msgid="6183558856175159629">"აუდიოს ჩაწერა"</string>
@@ -371,11 +373,11 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"ამ ფუნქციის მომწოდებელ სერვისს ექნება წვდომა ყველა ინფორმაციაზე, რომელიც თქვენს ეკრანზე გამოჩნდება ან თქვენს მოწყობილობაზე დაიკვრება ჩაწერის ან ტრანსლირების განმავლობაში. აღნიშნული მოიცავს ისეთ ინფორმაციას, როგორიც არის პაროლები, გადახდის დეტალები, ფოტოები, შეტყობინებები და თქვენ მიერ დაკრული აუდიო."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"დაიწყოს ჩაწერა ან ტრანსლირება?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"დაიწყოს ჩაწერა ან ტრანსლირება <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-ით?"</string>
-    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"გსურთ დართოთ ნება <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> გაზიარების ან ჩაწერისთვის?"</string>
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"გსურთ, დართოთ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-ს გაზიარების ან ჩაწერის ნება?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"მთელი ეკრანი"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"ერთი აპი"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"როდესაც თქვენ აზიარებთ, ჩაწერთ ან ტრანსლირებთ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> აქვს წვდომა ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> აქვს წვდომა აქვს ყველაფერზე, რაც ჩანს აპში ან ითამაშეთ. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-ს აქვს წვდომა ყველაფერზე, რაც ამ აპში ჩანს და მასშია გაშვებული. შესაბამისად, გამოიჩინეთ სიფრთხილე პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან თუ სხვა მგრძნობიარე ინფორმაციასთან დაკავშირებით."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"გაგრძელება"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"გააზიარეთ ან ჩაწერეთ აპი"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"გსურთ ამ აპისთვის გაზიარების ან ჩაწერის უფლების მიცემა?"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ავტომატური"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"ხმისა და ვიბრაციის გარეშე"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ხმისა და ვიბრაციის გარეშე, ჩნდება მიმოწერების სექციის ქვედა ნაწილში"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"დარეკვა ან ვიბრაცია ტელეფონის პარამეტრების მიხედვით"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"დარეკვა ან ვიბრაცია ტელეფონის პარამეტრების მიხედვით. მიმოწერები <xliff:g id="APP_NAME">%1$s</xliff:g>-ის ბუშტიდან, ნაგულისხმევად."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"დარეკვა ან ვიბრაცია მოწყობილობის პარამეტრების მიხედვით"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"დარეკვა ან ვიბრაცია მოწყობილობის პარამეტრების მიხედვით. მიმოწერები <xliff:g id="APP_NAME">%1$s</xliff:g>-ის ბუშტიდან, ნაგულისხმევად."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"სისტემისთვის ისეთი უფლების მინიჭება, რომ მან განსაზღვროს, ამ შეტყობინებამ ხმოვანი სიგნალი უნდა აამოქმედოს თუ ვიბრაცია"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;სტატუსი:&lt;/b&gt; ნაგულისხმევად გარდაქმნილი"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;სტატუსი:&lt;/b&gt; „უხმო“ სტატუსზე გადასული"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"აირჩიეთ აპი მართვის საშუალებების დასამატებლად"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{დაემატა მართვის # საშუალება.}other{დაემატა მართვის # საშუალება.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"ამოიშალა"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"გსურთ <xliff:g id="APPNAME">%s</xliff:g>-ის დამატება?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"როდესაც <xliff:g id="APPNAME">%s</xliff:g>-ს ამატებთ, მან შეიძლება დაამატოს მართვის საშუალებები და კონტენტი მოცემულ არეში. ზოგიერთ აპში შეგიძლიათ აირჩიოთ, რომელი მართვის საშუალებები უნდა გამოჩნდეს აქ."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"რჩეულებშია"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"რჩეულებშია, პოზიციაზე <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"რჩეულებიდან ამოღებულია"</string>
@@ -848,13 +852,13 @@
     <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"გახსენით <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g>-დან"</string>
-    <string name="media_transfer_undo" msgid="1895606387620728736">"მოქმედების გაუქმება"</string>
+    <string name="media_transfer_undo" msgid="1895606387620728736">"მოქმედ.გაუქმება"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"მიიტანეთ უფრო ახლოს, რომ დაუკრათ <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"მიუახლოვდით <xliff:g id="DEVICENAME">%1$s</xliff:g>-ს მისი მეშვეობით დასაკრავად"</string>
-    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"მიმდინარეობს დაკვრა <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"აქ სათამაშოდ, მიუახლოვდით <xliff:g id="DEVICENAME">%1$s</xliff:g>-ს"</string>
+    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"უკრავს <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"რაღაც შეცდომა მოხდა. ცადეთ ხელახლა."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"იტვირთება"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ტაბლეტი"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"არააქტიურია, გადაამოწმეთ აპი"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"ვერ მოიძებნა"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"კონტროლი მიუწვდომელია"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"შეცდომა, ისევ ცადეთ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"მართვის საშუალებების დამატება"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"მართვის საშუალებათა რედაქტირება"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"აპის დამატება"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"მედია-გამოსავლების დამატება"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ჯგუფი"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"არჩეულია 1 მოწყობილობა"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ხმა"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"დინამიკები და დისპლეები"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"შემოთავაზებული მოწყობილობები"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ტრანსლირების მუშაობის პრინციპი"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ტრანსლაცია"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"თქვენთან ახლოს მყოფ ხალხს თავსებადი Bluetooth მოწყობილობით შეუძლიათ თქვენ მიერ ტრანსლირებული მედიის მოსმენა"</string>
@@ -1008,12 +1014,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• ხელმისაწვდომია მინიმუმ ერთი მოწყობილობა"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"შეხების დაamp; მოცდის მალსახმობი"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"გაუქმება"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"გადაატრიალეთ ახლა"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"ახლა გადატრიალება"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"გაშალეთ ტელეფონი უკეთესი სელფისთვის"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"გადააბრუნეთ წინა ეკრანზე უკეთესი სელფის მისაღებად?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"გამოვიყენოთ წინა ეკრანი უკეთესი სელფის მისაღებად?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"გამოიყენეთ უკანა კამერა უფრო ფართო ფოტოს გადასაღებად მაღალი გარჩევადობით."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ეს ეკრანი გამოირთვება"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"დასაკეცი მოწყობილობა იხსნება"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"დასაკეცი მოწყობილობა ტრიალებს"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"დარჩენილია ბატარეის <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"დააკავშირეთ თქვენი სტილუსი დამტენს"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"სტილუსის ბატარეა დაცლის პირასაა"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ვიდეოკამერა"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"ამ პროფილიდან დარეკვა ვერ ხერხდება"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"თქვენი სამსახურის წესები საშუალებას გაძლევთ, სატელეფონო ზარები განახორციელოთ მხოლოდ სამსახურის პროფილიდან"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"სამსახურის პროფილზე გადართვა"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"დახურვა"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index bb24d6a..a3b330c 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Төменгі шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Сол жақ шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Оң жақ шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Жұмыс профилінен алынған скриншоттар <xliff:g id="APP">%1$s</xliff:g> қолданбасында сақталады."</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Экран жазғыш"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Экран жазғыш бейнесін өңдеу"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды бейнеге жазудың ағымдағы хабарландыруы"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматты"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Дыбыс не діріл болмайды."</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Дыбыс не діріл болмайды, әңгімелер бөлімінің төмен жағында тұрады."</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Телефон параметрлеріне байланысты дыбыстық сигнал не діріл болуы мүмкін."</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Телефон параметрлеріне байланысты дыбыстық сигнал не діріл болуы мүмкін. <xliff:g id="APP_NAME">%1$s</xliff:g> әңгімелері әдепкісінше қалқып шығады."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Құрылғы параметрлеріне байланысты шырылдауы не дірілдеуі мүмкін"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Құрылғы параметрлеріне байланысты шырылдауы не дірілдеуі мүмкін. <xliff:g id="APP_NAME">%1$s</xliff:g> чаттары әдепкісінше қалқымалы етіп көрсетіледі."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Хабарландыру дыбысының немесе дірілдің қосылуын жүйе анықтайтын болады"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Күйі:&lt;/b&gt; \"Әдепкі\" санатына көтерілген"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Күйі:&lt;/b&gt; \"Үнсіз\" санатына төмендетілген"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Басқару элементтері қосылатын қолданбаны таңдаңыз"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# басқару элементі қосылды.}other{# басқару элементі қосылды.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Өшірілді"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Таңдаулыларға қосылды"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Таңдаулыларға қосылды, <xliff:g id="NUMBER">%d</xliff:g>-позиция"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Таңдаулылардан алып тасталды"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> қолданбасында \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Қайтару"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында музыка ойнату үшін оған жақындаңыз."</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Осы жерде ойнау үшін <xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысына жақындаңыз"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Осы жерде ойнату үшін <xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысына жақындаңыз."</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында ойнатылуда."</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Бірдеңе дұрыс болмады. Қайталап көріңіз."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Жүктеліп жатыр"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Өшірулі. Қолданба тексеріңіз."</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Табылмады"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Басқару виджеті қолжетімсіз"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Қате шықты. Қайталап көріңіз."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Басқару элементтерін қосу"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Басқару элементтерін өзгерту"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Шығыс сигналдарды қосу"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 құрылғы таңдалды."</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Дыбыс деңгейі"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер мен дисплейлер"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ұсынылған құрылғылар"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Тарату қалай жүзеге асады"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Тарату"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Үйлесімді Bluetooth құрылғылары бар маңайдағы адамдар сіз таратып жатқан медиамазмұнды тыңдай алады."</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Бұл экран өшіріледі."</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Бүктемелі құрылғы ашылып жатыр."</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Бүктемелі құрылғы аударылып жатыр."</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Қалған батарея заряды: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Стилусты зарядтағышқа жалғаңыз."</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Стилус батареясының заряды аз"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Бейнекамера"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Бұл профильден қоңырау шалу мүмкін емес"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Жұмыс саясатыңызға сәйкес тек жұмыс профилінен қоңырау шалуға болады."</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Жұмыс профиліне ауысу"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Жабу"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 6595305..05d0429 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"បន្ទាត់បែងចែក​ខាងក្រោម <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"បន្ទាត់បែងចែក​ខាងឆ្វេង <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"បន្ទាត់បែងចែក​ខាងស្ដាំ <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"រូបថតអេក្រង់​ក្នុងកម្រងព័ត៌មានការងារ​ត្រូវបានរក្សាទុកនៅក្នុងកម្មវិធី <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ឯកសារ"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"មុខងារថត​វីដេអូអេក្រង់"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"កំពុង​ដំណើរការ​ការថតអេក្រង់"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ការជូនដំណឹង​ដែល​កំពុង​ដំណើរការ​សម្រាប់​រយៈពេលប្រើ​ការថត​សកម្មភាព​អេក្រង់"</string>
@@ -371,7 +373,7 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"សេវាកម្មដែល​ផ្ដល់​មុខងារ​នេះ​នឹងមាន​សិទ្ធិ​ចូលប្រើ​ព័ត៌មាន​ទាំងអស់​ដែល​អាច​មើលឃើញ​នៅលើ​អេក្រង់​របស់អ្នក ឬ​ដែលចាក់​ពីឧបករណ៍​របស់អ្នក នៅពេល​កំពុង​ថត ឬភ្ជាប់។ ព័ត៌មាន​នេះមាន​ដូចជា ពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិត​អំពីការទូទាត់​ប្រាក់ រូបថត សារ និង​សំឡេង​ដែល​អ្នកចាក់​ជាដើម។"</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"ចាប់ផ្ដើម​ថត ឬភ្ជាប់​មែនទេ?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"ចាប់ផ្ដើម​ថត ឬភ្ជាប់​ដោយប្រើ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ឬ?"</string>
-    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"អនុញ្ញាតឱ្យ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ចែករំលែក ឬថតទេ?"</string>
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"អនុញ្ញាតឱ្យ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ចែករំលែក ឬថត?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"អេក្រង់ទាំងមូល"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"កម្មវិធីតែមួយ"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬបញ្ជូន <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញឱ្យឃើញនៅលើអេក្រង់របស់អ្នក ឬលេងនៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មាន​លម្អិតអំពី​ការ​ទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ស្វ័យប្រវត្តិ"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"គ្មាន​សំឡេង ឬការញ័រទេ"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"គ្មានសំឡេង​ឬការញ័រ និងបង្ហាញ​ទាបជាង​នៅក្នុង​ផ្នែកសន្ទនា"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"អាចរោទ៍ ឬញ័រ ដោយផ្អែកលើ​ការកំណត់​ទូរសព្ទ"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"អាចរោទ៍ ឬញ័រ ដោយផ្អែកលើ​ការកំណត់​ទូរសព្ទ។ ការសន្ទនា​ពី​ពពុះ <xliff:g id="APP_NAME">%1$s</xliff:g> តាម​លំនាំដើម​។"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"អាចរោទ៍ ឬញ័រ ដោយផ្អែកលើ​ការកំណត់ឧបករណ៍"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"អាចរោទ៍ ឬញ័រ ដោយផ្អែកលើ​ការកំណត់ឧបករណ៍។ ការសន្ទនា​ពីផ្ទាំងអណ្ដែត <xliff:g id="APP_NAME">%1$s</xliff:g> តាម​លំនាំដើម​។"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ឱ្យប្រព័ន្ធកំណត់ថាតើ​ការជូនដំណឹងនេះ​គួរតែបន្លឺសំឡេង ឬញ័រ"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;ស្ថានភាព៖&lt;/b&gt; បានដំឡើងទៅ​លំនាំដើម"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;ស្ថានភាព៖&lt;/b&gt; បានបញ្ចុះទៅស្ងាត់"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"ជ្រើសរើស​កម្មវិធីដែលត្រូវបញ្ចូល​ផ្ទាំងគ្រប់គ្រង"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{បានបញ្ចូល​ការគ្រប់គ្រង #។}other{បានបញ្ចូល​ការគ្រប់គ្រង #។}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"បានដកចេញ"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"បានដាក់ជា​សំណព្វ"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"បានដាក់ជា​សំណព្វ ទីតាំង​ទី <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"បានដកចេញ​ពី​សំណព្វ"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ពី <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"ត្រឡប់វិញ"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"រំកិលឱ្យកាន់តែជិត ដើម្បីចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"រំកិលឱ្យកាន់តែជិត <xliff:g id="DEVICENAME">%1$s</xliff:g> ដើម្បីចាក់នៅទីនេះ"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ដើម្បីចាក់នៅទីនេះ សូមខិតទៅជិត <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"កំពុង​ចាក់​​នៅ​លើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"មានអ្វីមួយខុសប្រក្រតី។ សូមព្យាយាមម្ដងទៀត។"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"កំពុងផ្ទុក"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ថេប្លេត"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"អសកម្ម ពិនិត្យមើល​កម្មវិធី"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"រកមិន​ឃើញទេ"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"មិនអាច​គ្រប់គ្រង​បានទេ"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"មានបញ្ហា សូម​ព្យាយាម​ម្តងទៀត"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"បញ្ចូល​ផ្ទាំងគ្រប់គ្រង"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"កែ​ផ្ទាំងគ្រប់គ្រង"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"បញ្ចូល​ឧបករណ៍​មេឌៀ"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ក្រុម"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"បានជ្រើសរើស​ឧបករណ៍ 1"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"កម្រិតសំឡេង"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ឧបករណ៍បំពងសំឡេង និងផ្ទាំងអេក្រង់"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ឧបករណ៍​ដែលបានណែនាំ"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"របៀបដែលការផ្សាយដំណើរការ"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ការ​ផ្សាយ"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"មនុស្សនៅជិត​អ្នកដែលមាន​ឧបករណ៍ប៊្លូធូស​ត្រូវគ្នា​អាចស្តាប់​មេឌៀ​ដែលអ្នកកំពុងផ្សាយបាន"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ អេក្រង់នេះនឹងបិទ"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ឧបករណ៍អាច​បត់បានកំពុងត្រូវបានលា"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ឧបករណ៍អាច​បត់បានកំពុងត្រូវបានលា"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ថ្មនៅសល់ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ភ្ជាប់ប៊ិករបស់អ្នកជាមួយឆ្នាំងសាក"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ថ្មប៊ិកនៅសល់តិច"</string>
+    <string name="video_camera" msgid="7654002575156149298">"កាមេរ៉ា​វីដេអូ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"មិនអាចហៅទូរសព្ទពីកម្រងព័ត៌មាននេះបានទេ"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"គោលការណ៍ការងាររបស់អ្នកអនុញ្ញាតឱ្យអ្នកធ្វើការហៅទូរសព្ទបានតែពីកម្រងព័ត៌មានការងារប៉ុណ្ណោះ"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"ប្ដូរ​ទៅ​កម្រង​ព័ត៌មាន​ការងារ"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"បិទ"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 81a37f3..e543780 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ಕೆಳಗಿನ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ಎಡಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ಬಲಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"<xliff:g id="APP">%1$s</xliff:g> ಆ್ಯಪ್‌ನಲ್ಲಿ ಉಳಿಸಲಾದ ಕೆಲಸದ ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳು"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ಫೈಲ್‌ಗಳು"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡರ್"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೆಶನ್‌ಗಾಗಿ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಅಧಿಸೂಚನೆ"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ಸ್ವಯಂಚಾಲಿತ"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"ಯಾವುದೇ ಧ್ವನಿ ಅಥವಾ ವೈಬ್ರೇಷನ್‌ ಆಗುವುದಿಲ್ಲ"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ಯಾವುದೇ ಧ್ವನಿ ಅಥವಾ ವೈಬ್ರೇಷನ್‌ ಆಗುವುದಿಲ್ಲ, ಸಂಭಾಷಣೆ ವಿಭಾಗದ ಕೆಳಭಾಗದಲ್ಲಿ ಗೋಚರಿಸುತ್ತದೆ"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಆಧರಿಸಿ ಫೋನ್ ರಿಂಗ್ ಅಥವಾ ವೈಬ್ರೇಟ್ ಆಗುತ್ತದೆ"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಆಧರಿಸಿ ಫೋನ್ ರಿಂಗ್ ಅಥವಾ ವೈಬ್ರೇಟ್ ಆಗುತ್ತದೆ. ಡಿಫಾಲ್ಟ್ ಆಗಿ, <xliff:g id="APP_NAME">%1$s</xliff:g> ನ ಬಬಲ್ ಸಂಭಾಷಣೆಗಳು."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಆಧರಿಸಿ ಸಾಧನ ರಿಂಗ್ ಅಥವಾ ವೈಬ್ರೇಟ್ ಆಗುತ್ತದೆ"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಆಧರಿಸಿ ಫೋನ್ ರಿಂಗ್ ಅಥವಾ ವೈಬ್ರೇಟ್ ಆಗುತ್ತದೆ. ಡಿಫಾಲ್ಟ್ ಆಗಿ, <xliff:g id="APP_NAME">%1$s</xliff:g> ಬಬಲ್ ಸಂಭಾಷಣೆಗಳು."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ಈ ಅಧಿಸೂಚನೆಯು ಶಬ್ದ ಮಾಡಬೇಕೇ ಅಥವಾ ವೈಬ್ರೇಟ್ ಮಾಡಬೇಕೇ ಎಂಬುದನ್ನು ನಿರ್ಧರಿಸುವ ಅವಕಾಶವನ್ನು ಸಿಸ್ಟಂಗೆ ನೀಡಿ"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;ಸ್ಥಿತಿ:&lt;/b&gt; ಡೀಫಾಲ್ಟ್‌ಗೆ ಬಡ್ತಿ ಹೊಂದಿದೆ"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;ಸ್ಥಿತಿ:&lt;/b&gt; ಸೈಲೆಂಟ್‌ಗೆ ಕೆಳದರ್ಜೆಗೆ ಇಳಿದಿದೆ"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ನಿಯಂತ್ರಣವನ್ನು ಸೇರಿಸಲಾಗಿದೆ.}one{# ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ.}other{# ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಲಾಗಿದೆ.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> ಅನ್ನು ಸೇರಿಸಬೇಕೆ?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"ನೀವು <xliff:g id="APPNAME">%s</xliff:g> ಅನ್ನು ಸೇರಿಸಿದಾಗ, ಅದು ಈ ಪ್ಯಾನೆಲ್‌ಗೆ ನಿಯಂತ್ರಣಗಳು ಮತ್ತು ವಿಷಯವನ್ನು ಸೇರಿಸಬಹುದು. ಕೆಲವು ಆ್ಯಪ್‌ಗಳಲ್ಲಿ, ಇಲ್ಲಿ ಯಾವ ನಿಯಂತ್ರಣಗಳು ಕಾಣಿಸಬೇಕು ಎಂಬುದನ್ನು ನೀವು ಆಯ್ಕೆಮಾಡಬಹುದು."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"ಮೆಚ್ಚಲಾಗಿರುವುದು"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ಮೆಚ್ಚಲಾಗಿರುವುದು, ಸ್ಥಾನ <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ಮೆಚ್ಚಿನದಲ್ಲದ್ದು"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%2$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"ರದ್ದುಗೊಳಿಸಿ"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು ಅದರ ಹತ್ತಿರಕ್ಕೆ ಸರಿಯಿರಿ"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ಇಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು <xliff:g id="DEVICENAME">%1$s</xliff:g> ಸಮೀಪಕ್ಕೆ ಹೋಗಿ"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ಇಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು, <xliff:g id="DEVICENAME">%1$s</xliff:g> ಸಮೀಪಕ್ಕೆ ಸರಿಯಿರಿ"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಆಗುತ್ತಿದೆ"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"ಏನೋ ತಪ್ಪಾಗಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"ಲೋಡ್ ಆಗುತ್ತಿದೆ"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ಟ್ಯಾಬ್ಲೆಟ್"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"ಕಂಡುಬಂದಿಲ್ಲ"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"ನಿಯಂತ್ರಣ ಲಭ್ಯವಿಲ್ಲ"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"ದೋಷ, ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಿ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ನಿಯಂತ್ರಣಗಳನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ಆ್ಯಪ್ ಅನ್ನು ಸೇರಿಸಿ"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ಔಟ್‌ಪುಟ್‌ಗಳನ್ನು ಸೇರಿಸಿ"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ಗುಂಪು"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ಸಾಧನವನ್ನು ಆಯ್ಕೆ ಮಾಡಲಾಗಿದೆ"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ವಾಲ್ಯೂಮ್"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ಸ್ಪೀಕರ್‌ಗಳು ಮತ್ತು ಡಿಸ್‌ಪ್ಲೇಗಳು"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ಸೂಚಿಸಿದ ಸಾಧನಗಳು"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ಪ್ರಸಾರವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ಪ್ರಸಾರ"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ಹೊಂದಾಣಿಕೆಯಾಗುವ ಬ್ಲೂಟೂತ್ ಸಾಧನಗಳನ್ನು ಹೊಂದಿರುವ ಸಮೀಪದಲ್ಲಿರುವ ಜನರು ನೀವು ಪ್ರಸಾರ ಮಾಡುತ್ತಿರುವ ಮಾಧ್ಯಮವನ್ನು ಆಲಿಸಬಹುದು"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ಈ ಸ್ಕ್ರೀನ್ ಆಫ್ ಆಗುತ್ತದೆ"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಅನ್‌ಫೋಲ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಸುತ್ತಲೂ ತಿರುಗಿಸಲಾಗುತ್ತಿದೆ"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ ಉಳಿದಿದೆ"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ನಿಮ್ಮ ಸ್ಟೈಲಸ್ ಅನ್ನು ಚಾರ್ಜರ್‌ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ಸ್ಟೈಲಸ್ ಬ್ಯಾಟರಿ ಕಡಿಮೆಯಿದೆ"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ವೀಡಿಯೊ ಕ್ಯಾಮರಾ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"ಈ ಪ್ರೊಫೈಲ್‌ನಿಂದ ಕರೆ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"ನಿಮ್ಮ ಕೆಲಸದ ನೀತಿಯು ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ನಿಂದ ಮಾತ್ರ ಫೋನ್ ಕರೆಗಳನ್ನು ಮಾಡಲು ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ಗೆ ಬದಲಿಸಿ"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"ಮುಚ್ಚಿರಿ"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 1570a6a..ad5b523 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"하단 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"왼쪽 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"오른쪽 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"직장 프로필의 스크린샷은 <xliff:g id="APP">%1$s</xliff:g> 앱에 저장됩니다."</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"파일"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"화면 녹화"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"화면 녹화 처리 중"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"화면 녹화 세션에 관한 지속적인 알림"</string>
@@ -371,11 +373,11 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"이 기능을 제공하는 서비스는 녹화 또는 전송 중에 화면에 표시되거나 기기에서 재생되는 모든 정보에 액세스할 수 있습니다. 여기에는 비밀번호, 결제 세부정보, 사진, 메시지, 재생하는 오디오 같은 정보가 포함됩니다."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"녹화 또는 전송을 시작하시겠습니까?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>으로 녹화 또는 전송을 시작하시겠습니까?"</string>
-    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 공유 또는 녹화를 허용할까요?"</string>
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 공유 또는 녹화하도록 허용할까요?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"전체 화면"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"단일 앱"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"공유하거나 녹화하거나 전송할 때 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 앱에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"앱을 공유하거나 녹화하거나 전송할 때는 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"앱을 공유하거나 녹화하거나 전송할 때는 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 해당 앱에 표시되거나 앱에서 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"계속"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"앱 공유 또는 녹화"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"앱에서 공유하거나 기록하도록 허용하시겠습니까?"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"자동"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"소리 또는 진동 없음"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"소리나 진동이 울리지 않으며 대화 섹션 하단에 표시됨"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"휴대전화 설정에 따라 벨소리나 진동이 울릴 수 있음"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"휴대전화 설정에 따라 벨소리나 진동이 울릴 수 있습니다. 기본적으로 <xliff:g id="APP_NAME">%1$s</xliff:g>의 대화는 대화창으로 표시됩니다."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"기기 설정에 따라 벨소리나 진동이 울릴 수 있음"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"기기 설정에 따라 벨소리나 진동이 울릴 수 있습니다. 기본적으로 <xliff:g id="APP_NAME">%1$s</xliff:g>의 대화는 대화창으로 표시됩니다."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"시스템에서 알림 시 소리 또는 진동을 사용할지 결정하도록 허용합니다."</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;상태:&lt;/b&gt; 기본으로 높임"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;상태:&lt;/b&gt; 무음으로 낮춤"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"컨트롤을 추가할 앱을 선택하세요"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{설정이 #개 추가되었습니다.}other{설정이 #개 추가되었습니다.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"삭제됨"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"즐겨찾기에 추가됨"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"즐겨찾기에 추가됨, 위치 <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"즐겨찾기에서 삭제됨"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>에서 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"실행취소"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생하려면 기기를 더 가까이로 옮기세요."</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"현재 기기에서 재생하려면 <xliff:g id="DEVICENAME">%1$s</xliff:g>에 더 가까이 이동합니다."</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"여기에서 재생하려면 <xliff:g id="DEVICENAME">%1$s</xliff:g>에 더 가까이 이동하세요."</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생 중"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"문제가 발생했습니다. 다시 시도해 주세요."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"로드 중"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"태블릿"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"비활성. 앱을 확인하세요."</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"찾을 수 없음"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"컨트롤을 사용할 수 없음"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"오류. 다시 시도하세요."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"컨트롤 추가"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"컨트롤 수정"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"출력 추가"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"그룹"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"기기 1대 선택됨"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"볼륨"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"스피커 및 디스플레이"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"추천 기기"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"브로드캐스팅 작동 원리"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"브로드캐스트"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"호환되는 블루투스 기기를 가진 근처의 사용자가 내가 브로드캐스트 중인 미디어를 수신 대기할 수 있습니다."</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ 이 화면이 꺼집니다."</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"폴더블 기기를 펼치는 모습"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"폴더블 기기를 뒤집는 모습"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"배터리 <xliff:g id="PERCENTAGE">%s</xliff:g> 남음"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"스타일러스를 충전기에 연결하세요"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"스타일러스 배터리 부족"</string>
+    <string name="video_camera" msgid="7654002575156149298">"비디오 카메라"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"이 프로필에서 전화를 걸 수 없음"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"직장 정책이 직장 프로필에서만 전화를 걸도록 허용합니다."</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"직장 프로필로 전환"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"닫기"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index b120684..5c6166a 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ылдый жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Сол жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Оң жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Жумуш скриншоттору <xliff:g id="APP">%1$s</xliff:g> колдонмосунда сакталат"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"экрандан видео жаздырып алуу"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Экрандан жаздырылып алынган видео иштетилүүдө"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды жаздыруу сеансы боюнча учурдагы билдирме"</string>
@@ -371,16 +373,16 @@
     <string name="media_projection_dialog_service_text" msgid="958000992162214611">"Жаздырып же тышкы экранга чыгарып жатканда, бул колдонмо экраныңыздагы бардык маалыматты же түзмөктө ойнолуп жаткан бардык нерселерди (сырсөздөрдү, төлөмдүн чоо-жайын, сүрөттөрдү, билдирүүлөрдү жана угуп жаткан аудиофайлдарды) көрө алат."</string>
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Жаздырып же тышкы экранга чыгарып баштайсызбы?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> колдонмосу аркылуу жаздырып же тышкы экранга чыгарып баштайсызбы?"</string>
-    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> колдонмосуна бөлүшүүгө же жаздырууга уруксат бересизби?"</string>
+    <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> колдонмосуна экранды бөлүшүүгө же андан видео тартууга уруксат бересизби?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Бүтүндөй экран"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Жалгыз колдонмо"</string>
-    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Бөлүшүп, жаздырып же тышкы экранда бөлүшкөндө <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> экраныңызда көрүнүп жана түзмөктө ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөм маалыматын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Бөлүшүп, жаздырып же тышкы экранда бөлүшкөндө <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ал колдонмодо көрүнүп жана ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөм маалыматын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Экранды көрсөтүп, тышка чыгарып же андан видео тартып жатканда, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> түзмөктүн экранындагы нерселердин баарын көрө алат. Андыктан сырсөздөр, төлөм маалыматы, билдирүүлөр сыяктуу купуя маалыматты киргизүүдө же көрүүдө этият болуңуз."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Экранды көрсөтүп, тышка чыгарып же андан видео тартып жатканда, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> түзмөктүн экранындагы нерселердин баарын көрө алат. Андыктан сырсөздөр, төлөм маалыматы, билдирүүлөр сыяктуу купуя маалыматты киргизүүдө же көрүүдө этият болуңуз."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Улантуу"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Колдонмону бөлүшүү же жаздыруу"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Бул колдонмого бөлүшүп же жаздырууга уруксат бересизби?"</string>
-    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Бөлүшүп, жаздырып же тышкы экранга чыгарганда бул колдонмо экраныңызда көрүнүп жана түзмөктө ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
-    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Бөлүшүп, жаздырып же тышкы экранга чыгарганда бул колдонмо ал колдонмодо көрсөтүлүп жана ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Экранды көрсөтүп, тышка чыгарып же андан видео тартып жатканда, бул колдонмо түзмөктүн экранындагы нерселердин баарын көрө алат. Андыктан сырсөздөр, төлөм маалыматы, билдирүүлөр сыяктуу купуя маалыматты киргизүүдө же көрүүдө этият болуңуз."</string>
+    <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Экранды көрсөтүп, тышка чыгарып же андан видео тартып жатканда, бул колдонмо түзмөктүн экранындагы нерселердин баарын көрө алат. Андыктан сырсөздөр, төлөм маалыматы, билдирүүлөр сыяктуу купуя маалыматты киргизүүдө же көрүүдө этият болуңуз."</string>
     <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT администраторуңуз бөгөттөп койгон"</string>
     <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Түзмөк саясаты экрандагыны тартып алууну өчүрүп койгон"</string>
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
@@ -422,7 +424,7 @@
     <string name="monitoring_button_view_policies" msgid="3869724835853502410">"Саясаттарды карап көрүү"</string>
     <string name="monitoring_button_view_controls" msgid="8316440345340701117">"Башкаруу элементтерин көрүү"</string>
     <string name="monitoring_description_named_management" msgid="505833016545056036">"Бул түзмөк <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> уюмуна таандык.\n\nАдминистраторуңуз бул түзмөктөгү жөндөөлөрдү, корпоративдик ресурстарды пайдалануу мүмкүнчүлүгүн берген параметрлерди жана колдонмолорду, түзмөгүңүзгө байланыштуу маалыматтарды (мисалы, түзмөгүңүздүн жайгашкан жери сыяктуу) көзөмөлдөп башкара алат.\n\nТолугураак маалымат алуу үчүн IT администраторуңузга кайрылыңыз."</string>
-    <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"<xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> бул түзмөк менен байланышкан маалыматты көрүп, колдонмолорду башкарып, анын жөндөөлөрүн өзгөртө алат.\n\nЭгер суроолоруңуз болсо, <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g> уюмуна кайрылыңыз."</string>
+    <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"<xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> бул түзмөк менен байланышкан маалыматты көрүп, колдонмолорду башкарып, анын параметрлерин өзгөртө алат.\n\nЭгер суроолоруңуз болсо, <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g> уюмуна кайрылыңыз."</string>
     <string name="monitoring_description_management" msgid="4308879039175729014">"Бул түзмөк уюмуңузга таандык.\n\nАдминистраторуңуз бул түзмөктөгү жөндөөлөрдү, корпоративдик ресурстарды пайдалануу мүмкүнчүлүгүн берген параметрлерди жана колдонмолорду, түзмөгүңүзгө байланыштуу маалыматтарды (мисалы, түзмөгүңүздүн жайгашкан жери сыяктуу) көзөмөлдөп башкара алат.\n\nТолугураак маалымат алуу үчүн IT администраторуңузга кайрылыңыз."</string>
     <string name="monitoring_description_management_ca_certificate" msgid="7785013130658110130">"Ишканаңыз бул түзмөккө тастыктоочу борборду орнотту. Коопсуз тармагыңыздын трафиги көзөмөлдөнүп же өзгөртүлүшү мүмкүн."</string>
     <string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Ишканаңыз жумуш профилиңизге тастыктоочу борборду орнотту. Коопсуз тармагыңыздын трафиги көзөмөлдөнүп же өзгөртүлүшү мүмкүн."</string>
@@ -439,7 +441,7 @@
     <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
     <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Ишеним агенти кулпусун ачты"</string>
     <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
-    <string name="accessibility_volume_settings" msgid="1458961116951564784">"Добуштун жөндөөлөрү"</string>
+    <string name="accessibility_volume_settings" msgid="1458961116951564784">"Добуштун параметрлери"</string>
     <string name="volume_odi_captions_tip" msgid="8825655463280990941">"Автоматтык коштомо жазуулар"</string>
     <string name="accessibility_volume_close_odi_captions_tip" msgid="8924753283621160480">"Коштомо жазуулар кеңеши"</string>
     <string name="volume_odi_captions_content_description" msgid="4172765742046013630">"Коштомо жазуулардын үстүнө коюу"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматтык"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Үнү чыкпайт жана дирилдебейт"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Үнү чыкпайт же дирилдебейт жана сүйлөшүүлөр тизмесинин ылдый жагында көрүнөт"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Телефондун параметрлерине жараша шыңгырап же дирилдеши мүмкүн"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Телефондун параметрлерине жараша шыңгырап же дирилдеши мүмкүн. <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосундагы жазышуулар демейки жөндөө боюнча калкып чыкма билдирмелер түрүндө көрүнөт."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Түзмөктүн параметрлерине жараша шыңгырап же дирилдеши мүмкүн"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Түзмөктүн параметрлерине жараша шыңгырап же дирилдеши мүмкүн. <xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосундагы сүйлөшүүлөр демейки шартта калкып чыкма билдирмелер болуп көрүнөт."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Билдирменин үнүн чыгартууну же басууну тутумга тапшырыңыз"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Абалы:&lt;/b&gt; Демейкиге өзгөрдү"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Абалы:&lt;/b&gt; Үнсүз абалга төмөндөдү"</string>
@@ -745,7 +747,7 @@
     <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Жеткиликтүү болгондо мобилдик Интернет автоматтык түрдө которулбайт"</string>
     <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Жок, рахмат"</string>
     <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ооба, которулуу"</string>
-    <string name="touch_filtered_warning" msgid="8119511393338714836">"Уруксат берүү сурамыңыз көрүнбөй калгандыктан, Жөндөөлөр жообуңузду ырастай албай жатат."</string>
+    <string name="touch_filtered_warning" msgid="8119511393338714836">"Уруксат берүү сурамыңыз көрүнбөй калгандыктан, Параметрлер жообуңузду ырастай албай жатат."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосуна <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөтүүгө уруксат берилсинби?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунун маалыматын окуйт"</string>
     <string name="slice_permission_text_2" msgid="6758906940360746983">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунда аракеттерди аткарат"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Башкаруу элементтери кошула турган колдонмону тандоо"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# көзөмөл кошулду.}other{# көзөмөл кошулду.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Өчүрүлдү"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> кошулсунбу?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> колдонмосун кошсоңуз, ал бул панелге башкаруу элементтерин жана контентти кошо алат. Айрым колдонмолордо бул жерде көрүнүүчү башкаруу элементтерин тандай аласыз."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Сүйүктүүлөргө кошулду"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Сүйүктүүлөргө <xliff:g id="NUMBER">%d</xliff:g>-позицияга кошулду"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Сүйүктүүлөрдөн чыгарылды"</string>
@@ -810,7 +814,7 @@
     <string name="controls_favorite_removed" msgid="5276978408529217272">"Бардык башкаруу элементтери өчүрүлдү"</string>
     <string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"Өзгөртүүлөр сакталган жок"</string>
     <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"Башка колдонмолорду көрүү"</string>
-    <string name="controls_favorite_load_error" msgid="5126216176144877419">"Башкаруу элементтери жүктөлгөн жок. <xliff:g id="APP">%s</xliff:g> колдонмосуна өтүп, колдонмонун жөндөөлөрү өзгөрбөгөнүн текшериңиз."</string>
+    <string name="controls_favorite_load_error" msgid="5126216176144877419">"Башкаруу элементтери жүктөлгөн жок. <xliff:g id="APP">%s</xliff:g> колдонмосуна өтүп, колдонмонун параметрлери өзгөрбөгөнүн текшериңиз."</string>
     <string name="controls_favorite_load_none" msgid="7687593026725357775">"Шайкеш башкаруу элементтери жеткиликсиз"</string>
     <string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"Башка"</string>
     <string name="controls_dialog_title" msgid="2343565267424406202">"Түзмөктү башкаруу элементтерине кошуу"</string>
@@ -836,7 +840,7 @@
     <string name="controls_media_active_session" msgid="3146882316024153337">"Учурдагы медиа сеансын жашыруу мүмкүн эмес."</string>
     <string name="controls_media_dismiss_button" msgid="4485675693008031646">"Жашыруу"</string>
     <string name="controls_media_resume" msgid="1933520684481586053">"Улантуу"</string>
-    <string name="controls_media_settings_button" msgid="5815790345117172504">"Жөндөөлөр"</string>
+    <string name="controls_media_settings_button" msgid="5815790345117172504">"Параметрлер"</string>
     <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ыры (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотулуп жатат"</string>
     <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ичинен <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string>
     <string name="controls_media_button_play" msgid="2705068099607410633">"Ойнотуу"</string>
@@ -849,21 +853,22 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотуу"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын <xliff:g id="APP_LABEL">%2$s</xliff:g> колдонмосунан ойнотуу"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Кайтаруу"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүндө ойнотуу үчүн жакыныраак жылдырыңыз"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Бул жерде ойнотуу үчүн <xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүнө жакындатыңыз"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүндө ойнотуу үчүн жакындатыңыз"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ушул жерде ойнотуу үчүн <xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүнө жакындаңыз"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> аркылуу ойнотулууда"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Бир жерден ката кетти. Кайра аракет кылыңыз."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Жүктөлүүдө"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Жигерсиз. Колдонмону текшериңиз"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Табылган жок"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Башкара албайсыз"</string>
-    <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүн пайдалана албайсыз. Аны <xliff:g id="APPLICATION">%2$s</xliff:g> колдонмосунан башкарууга мүмкүн же мүмкүн эместигин, ошондой эле колдонмонун жөндөөлөрүнүн өзгөрүлбөгөнүн текшериңиз."</string>
+    <string name="controls_error_removed_message" msgid="2885911717034750542">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүн пайдалана албайсыз. Аны <xliff:g id="APPLICATION">%2$s</xliff:g> колдонмосунан башкарууга мүмкүн же мүмкүн эместигин, ошондой эле колдонмонун параметрлеринин өзгөрүлбөгөнүн текшериңиз."</string>
     <string name="controls_open_app" msgid="483650971094300141">"Колдонмону ачуу"</string>
     <string name="controls_error_generic" msgid="352500456918362905">"Абалы жүктөлгөн жок"</string>
     <string name="controls_error_failed" msgid="960228639198558525">"Ката, кайталап көрүңүз"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Башкаруу элементтерин кошуу"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Башкаруу элементтерин түзөтүү"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Колдонмо кошуу"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Медиа түзмөктөрдү кошуу"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 түзмөк тандалды"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Үндүн катуулугу"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер жана дисплейлер"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Сунушталган түзмөктөр"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Кабарлоо кантип иштейт"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Кабарлоо"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Шайкеш Bluetooth түзмөктөрү болгон жакын жердеги кишилер кабарлап жаткан медиаңызды уга алышат"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Бул экран өчөт"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ачылып турган бүктөлмө түзмөк"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Оодарылып жаткан бүктөлмө түзмөк"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Батареянын кубаты: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Стилусту кубаттаңыз"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Стилустун батареясы отурайын деп калды"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Видео камера"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Бул профилден чала албайсыз"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Жумуш саясатыңызга ылайык, жумуш профилинен гана чалууларды аткара аласыз"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Жумуш профилине которулуу"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Жабуу"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index fff2544..ac81dcc 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -64,4 +64,6 @@
 
     <dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
     <dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
+
+    <dimen name="controls_padding_horizontal">16dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 446deb05..c07470a 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ຂອບເຂດທາງລຸ່ມ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ຂອບເຂດທາງຊ້າຍ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ຂອບເຂດທາງຂວາ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ຮູບໜ້າຈໍວຽກຖືກບັນທຶກຢູ່ໃນແອັບ <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ໄຟລ໌"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"ໂປຣແກຣມບັນທຶກໜ້າຈໍ"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ກຳລັງປະມວນຜົນການບັນທຶກໜ້າຈໍ"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ການແຈ້ງເຕືອນສຳລັບເຊດຊັນການບັນທຶກໜ້າຈໍໃດໜຶ່ງ"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ອັດຕະໂນມັດ"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"ບໍ່ມີສຽງ ຫຼື ການສັ່ນເຕືອນ"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ບໍ່ມີສຽງ ຫຼື ການສັ່ນເຕືອນ ແລະ ປາກົດຢູ່ທາງລຸ່ມຂອງພາກສ່ວນການສົນທະນາ"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ອາດສົ່ງສຽງ ຫຼື ສັ່ນເຕືອນໂດຍອ້າງອີງຈາກການຕັ້ງຄ່າໂທລະສັບ"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ອາດສົ່ງສຽງ ຫຼື ສັ່ນເຕືອນໂດຍອ້າງອີງຈາກການຕັ້ງຄ່າໂທລະສັບ. ການສົນທະນາຈາກ <xliff:g id="APP_NAME">%1$s</xliff:g> ຈະສະແດງເປັນຟອງຕາມຄ່າເລີ່ມຕົ້ນ."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"ອາດສົ່ງສຽງ ຫຼື ສັ່ນເຕືອນໂດຍອ້າງອີງຈາກການຕັ້ງຄ່າອຸປະກອນ"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ອາດສົ່ງສຽງ ຫຼື ສັ່ນເຕືອນໂດຍອ້າງອີງຈາກການຕັ້ງຄ່າອຸປະກອນ. ການສົນທະນາຈາກ <xliff:g id="APP_NAME">%1$s</xliff:g> ຈະເປັນ bubble ຕາມຄ່າເລີ່ມຕົ້ນ."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ໃຫ້ລະບົບກຳນົດວ່າການແຈ້ງເຕືອນນິ້ຄວນມີສຽງ ຫຼື ສັ່ນເຕືອນຫຼືບໍ່"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;ສະຖານະ:&lt;/b&gt; ເລື່ອນລະດັບເປັນຄ່າເລີ່ມຕົ້ນແລ້ວ"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;ສະຖານະ:&lt;/b&gt; ຫຼຸດລະດັບເປັນປິດສຽງແລ້ວ"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"ເລືອກແອັບເພື່ອເພີ່ມການຄວບຄຸມ"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{ເພີ່ມ # ການຄວບຄຸມແລ້ວ.}other{ເພີ່ມ # ການຄວບຄຸມແລ້ວ.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"ລຶບອອກແລ້ວ"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"ເພີ່ມ <xliff:g id="APPNAME">%s</xliff:g> ບໍ?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"ເມື່ອທ່ານເພີ່ມ <xliff:g id="APPNAME">%s</xliff:g>, ມັນຈະສາມາດເພີ່ມການຄວບຄຸມ ແລະ ເນື້ອຫາໃສ່ແຜງນີ້ໄດ້. ໃນບາງແອັບ, ທ່ານສາມາດເລືອກວ່າຈະໃຫ້ສ່ວນຄວບຄຸມໃດສະແດງຂຶ້ນຢູ່ບ່ອນນີ້ໄດ້."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"ເພີ່ມລາຍການທີ່ມັກແລ້ວ"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ເພີ່ມລາຍການທີ່ມັກແລ້ວ, ຕຳແໜ່ງ <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ຍົກເລີກລາຍການທີ່ມັກແລ້ວ"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"ຍົກເລີກ"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"ຍ້າຍໄປໃກ້ຂຶ້ນເພື່ອຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ກະລຸນາຍ້າຍເຂົ້າໃກ້ <xliff:g id="DEVICENAME">%1$s</xliff:g> ເພື່ອຫຼິ້ນຢູ່ບ່ອນນີ້"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ເພື່ອຫຼິ້ນຢູ່ບ່ອນນີ້, ໃຫ້ເຂົ້າໃກ້ <xliff:g id="DEVICENAME">%1$s</xliff:g> ຫຼາຍຂຶ້ນ"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"ກຳລັງຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"ມີບາງຢ່າງຜິດພາດເກີດຂຶ້ນ. ກະລຸນາລອງໃໝ່."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"ກຳລັງໂຫຼດ"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ແທັບເລັດ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"ບໍ່ພົບ"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"ບໍ່ສາມາດໃຊ້ການຄວບຄຸມໄດ້"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"​ຜິດ​ພາດ​, ກະລຸນາລອງໃໝ່"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ເພີ່ມການຄວບຄຸມ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ແກ້ໄຂການຄວບຄຸມ"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ເພີ່ມແອັບ"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ເພີ່ມເອົ້າພຸດ"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ກຸ່ມ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"ເລືອກ 1 ອຸປະກອນແລ້ວ"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ລະດັບສຽງ"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ລຳໂພງ ແລະ ຈໍສະແດງຜົນ"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ອຸປະກອນທີ່ແນະນຳ"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ການອອກອາກາດເຮັດວຽກແນວໃດ"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ອອກອາກາດ"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ຄົນທີ່ຢູ່ໃກ້ທ່ານທີ່ມີອຸປະກອນ Bluetooth ທີ່ເຂົ້າກັນໄດ້ຈະສາມາດຟັງມີເດຍທີ່ທ່ານກຳລັງອອກອາກາດຢູ່ໄດ້"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ໜ້າຈໍນີ້ຈະປິດ"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ອຸປະກອນທີ່ພັບໄດ້ກຳລັງກາງອອກ"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ອຸປະກອນທີ່ພັກໄດ້ກຳລັງປີ້ນໄປມາ"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ແບັດເຕີຣີເຫຼືອ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ເຊື່ອມຕໍ່ປາກກາຂອງທ່ານກັບສາຍສາກ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ແບັດເຕີຣີປາກກາເຫຼືອໜ້ອຍ"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ກ້ອງວິ​ດີ​ໂອ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"ບໍ່ສາມາດໂທຈາກໂປຣໄຟລ໌ນີ້ໄດ້"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"ນະໂຍບາຍບ່ອນເຮັດວຽກຂອງທ່ານອະນຸຍາດໃຫ້ທ່ານໂທລະສັບໄດ້ຈາກໂປຣໄຟລ໌ບ່ອນເຮັດວຽກເທົ່ານັ້ນ"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"ສະຫຼັບໄປໃຊ້ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"ປິດ"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index db518c1..737e652 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Apatinė riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kairioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Dešinioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ekrano kopijos, užfiksuotos naudojantis darbo profiliu, išsaugomos programoje „<xliff:g id="APP">%1$s</xliff:g>“"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Failai"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Ekrano vaizdo įrašytuvas"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Apdorojam. ekrano vaizdo įraš."</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Šiuo metu rodomas ekrano įrašymo sesijos pranešimas"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatinis"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Neskamba ir nevibruoja"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Neskamba, nevibruoja ir rodoma apatinėje pokalbių skilties dalyje"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Gali skambėti arba vibruoti, atsižvelgiant į telefono nustatymus"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Gali skambėti arba vibruoti, atsižvelgiant į telefono nustatymus. Pokalbiai iš „<xliff:g id="APP_NAME">%1$s</xliff:g>“ debesėlio pagal numatytuosius nustatymus."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Gali skambėti arba vibruoti, atsižvelgiant į įrenginio nustatymus"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Gali skambėti arba vibruoti, atsižvelgiant į įrenginio nustatymus. Pokalbiai iš „<xliff:g id="APP_NAME">%1$s</xliff:g>“ debesėlio pagal numatytuosius nustatymus."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Nustatykite, kad sistema aptiktų, ar šis pranešimas turi skambėti, ar vibruoti"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Būsena:&lt;/b&gt; pakeista į numatytąjį lygį"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Būsena:&lt;/b&gt; pakeista į begarsį lygį"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Pasirinkite programą, kad pridėtumėte valdiklių"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Pridėtas # valdiklis.}one{Pridėtas # valdiklis.}few{Pridėti # valdikliai.}many{Pridėta # valdiklio.}other{Pridėta # valdiklių.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Pašalinta"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Pridėti „<xliff:g id="APPNAME">%s</xliff:g>“?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Pridėjus programą „<xliff:g id="APPNAME">%s</xliff:g>“, ji gali pridėti valdiklių ir turinio prie šio skydelio. Kai kuriose programose galite pasirinkti, kurie valdikliai čia rodomi."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Įtraukta į mėgstamiausius"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Įtraukta į mėgstamiausius, padėtis: <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Pašalinta iš mėgstamiausių"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Leisti „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%2$s</xliff:g>“"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Anuliuoti"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Prieikite arčiau, kad galėtumėte leisti įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Perkelkite arčiau „<xliff:g id="DEVICENAME">%1$s</xliff:g>“, kad būtų galima leisti čia"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Jei norite leisti čia, eikite arčiau įrenginio „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Leidžiama įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Kažkas ne taip. Bandykite dar kartą."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Įkeliama"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"planšetinis kompiuteris"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktyvu, patikrinkite progr."</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nerasta"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Valdiklis nepasiekiamas"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Klaida, bandykite dar kartą"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Pridėti valdiklių"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Redaguoti valdiklius"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Pridėti programą"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Išvesčių pridėjimas"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupė"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Pasirinktas 1 įrenginys"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Garsumas"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Garsiakalbiai ir ekranai"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Siūlomi įrenginiai"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kaip veikia transliacija"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Transliacija"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Netoliese esantys žmonės, turintys suderinamus „Bluetooth“ įrenginius, gali klausyti jūsų transliuojamos medijos"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Šis ekranas išsijungs"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Lankstomasis įrenginys išlankstomas"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Lankstomasis įrenginys apverčiamas"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Liko akumuliatoriaus įkrovos: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Prijunkite rašiklį prie kroviklio"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Senka rašiklio akumuliatorius"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Vaizdo kamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Negalima skambinti iš šio profilio"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Pagal jūsų darbo politiką galite skambinti telefonu tik iš darbo profilio"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Perjungti į darbo profilį"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Uždaryti"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 5ea8da2..9200573 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Apakšmala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kreisā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Labā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Darba profila ekrānuzņēmumi tiek saglabāti lietotnē <xliff:g id="APP">%1$s</xliff:g>."</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Ekrāna ierakstītājs"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekrāna ieraksta apstrāde"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Aktīvs paziņojums par ekrāna ierakstīšanas sesiju"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automātiski"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Nav skaņas signāla vai vibrācijas"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Nav skaņas signāla vai vibrācijas, kā arī atrodas tālāk sarunu sadaļā"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Atkarībā no tālruņa iestatījumiem var zvanīt vai vibrēt"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Atkarībā no tālruņa iestatījumiem var zvanīt vai vibrēt. Sarunas no lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> pēc noklusējuma tiek parādītas burbulī."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Atkarībā no iestatījumiem var zvanīt vai vibrēt"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Atkarībā no ierīces iestatījumiem var zvanīt vai vibrēt. Sarunas no lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> pēc noklusējuma tiek parādītas burbulī."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Iestatiet, lai sistēma noteiktu, vai šim paziņojumam būs skaņa vai vibrācija"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Statuss:&lt;/b&gt; svarīgums paaugstināts līdz noklusējumam"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Statuss:&lt;/b&gt; svarīgums pazemināts, un paziņojums tiks rādīts bez skaņas"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Izvēlieties lietotni, lai pievienotu vadīklas"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Pievienota # vadīkla.}zero{Pievienotas # vadīklas.}one{Pievienota # vadīkla.}other{Pievienotas # vadīklas.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Noņemta"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Vai pievienot lietotni <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Ja pievienosiet lietotni <xliff:g id="APPNAME">%s</xliff:g>, tā varēs pievienot vadīklas un saturu šim panelim. Dažās lietotnēs varat izvēlēties, kuras vadīklas šeit rādīt."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Pievienota izlasei"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Pievienota izlasei, <xliff:g id="NUMBER">%d</xliff:g>. pozīcija"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Noņemta no izlases"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” no lietotnes <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Atsaukt"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Pārvietojiet savu ierīci tuvāk, lai atskaņotu mūziku ierīcē “<xliff:g id="DEVICENAME">%1$s</xliff:g>”."</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Pārvietojieties tuvāk ierīcei “<xliff:g id="DEVICENAME">%1$s</xliff:g>”, lai atskaņotu šeit"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Lai atskaņotu šeit, jums jāatrodas tuvāk šai ierīcei: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Notiek atskaņošana ierīcē <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Radās kļūda. Mēģiniet vēlreiz."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Notiek ielāde"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"planšetdators"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktīva, pārbaudiet lietotni"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Netika atrasta"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Vadīkla nav pieejama"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Radās kļūda. Mēģiniet vēlreiz."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Pievienot vadīklas"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Rediģēt vadīklas"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Pievienot lietotni"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Izejas ierīču pievienošana"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Atlasīta viena ierīce"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Skaļums"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Skaļruņi un displeji"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ieteiktās ierīces"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kā darbojas apraide"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Apraide"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Tuvumā esošās personas ar saderīgām Bluetooth ierīcēm var klausīties jūsu apraidīto multivides saturu."</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Šis ekrāns tiks izslēgts."</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Salokāma ierīce tiek atlocīta"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Salokāma ierīce tiek apgriezta"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Atlikušais uzlādes līmenis: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Pievienojiet skārienekrāna pildspalvu lādētājam"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Zems skārienekrāna pildspalvas akumulatora līmenis"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nevar zvanīt no šī profila"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Saskaņā ar jūsu darba politiku tālruņa zvanus drīkst veikt tikai no darba profila"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Pārslēgties uz darba profilu"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Aizvērt"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 0575b10..5052c2c 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Долна граница <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лева граница <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Десна граница <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Сликите од екранот во работниот профил се зачувуваат во апликацијата <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Датотеки"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Снимач на екран"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Се обработува снимка од екран"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Тековно известување за сесија за снимање на екранот"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматски"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звук или вибрации"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звук или вибрации и се појавува подолу во делот со разговори"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Може да ѕвони или вибрира во зависност од поставките на телефонот"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Може да ѕвони или вибрира во зависност од поставките на телефонот. Стандардно, разговорите од <xliff:g id="APP_NAME">%1$s</xliff:g> се во балончиња."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Може да ѕвони или вибрира во зависност од поставките на уредот"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Може да ѕвони или вибрира во зависност од поставките на уредот. Стандардно, разговорите од <xliff:g id="APP_NAME">%1$s</xliff:g> се во балончиња."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Дозволете системот да определи дали известувањево треба да испушти звук или да вибрира"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Статус:&lt;/b&gt; поставено на „Стандардно“"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Статус:&lt;/b&gt; намалено на „Тивко“"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Изберете апликација за да додадете контроли"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Додадена е # контрола.}one{Додадени се # контрола.}other{Додадени се # контроли.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Отстранета"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Да се додаде <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Кога ќе ја додадете <xliff:g id="APPNAME">%s</xliff:g>, таа ќе може да додава контроли и содржини на таблава. Кај некои апликации, може да изберете кои контроли ќе се прикажуваат тука."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Омилена"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Омилена, позиција <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Неомилена"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Врати"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Приближете се за да пуштите на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Приближете се до <xliff:g id="DEVICENAME">%1$s</xliff:g> за да пуштите тука"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"За да пуштате овде, приближете се до <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Пуштено на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Нешто не е во ред. Обидете се повторно."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Се вчитува"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивна, провери апликација"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Не е најдено"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Контролата не е достапна"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Грешка, обидете се повторно"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Додајте контроли"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Изменете ги контролите"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Додајте апликација"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додајте излези"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Избран е 1 уред"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Јачина на звук"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Звучници и екрани"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Предложени уреди"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Како функционира емитувањето"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Емитување"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Луѓето во ваша близина со компатибилни уреди со Bluetooth може да ги слушаат аудиозаписите што ги емитувате"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Екранов ќе се исклучи"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Преклопувачки уред се отклопува"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Преклопувачки уред се врти"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Преостаната батерија: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Поврзете го пенкалото со полнач"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Слаба батерија на пенкало"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Видеокамера"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Не можете да се јавите од профилов"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Вашето работно правило ви дозволува да упатувате повици само од работниот профил"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Префрли се на работен профил"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Затвори"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 9a2353c..f4e461d 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"താഴെയുള്ള അതിർത്തി <xliff:g id="PERCENT">%1$d</xliff:g> ശതമാനം"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ഇടത് വശത്തെ അതിർത്തി <xliff:g id="PERCENT">%1$d</xliff:g> ശതമാനം"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"വലത് വശത്തെ അതിർത്തി <xliff:g id="PERCENT">%1$d</xliff:g> ശതമാനം"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ഔദ്യോഗിക പ്രൊഫൈലിന്റെ സ്ക്രീന്‍ഷോട്ടുകൾ <xliff:g id="APP">%1$s</xliff:g> ആപ്പിൽ സംരക്ഷിച്ചു"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ഫയലുകൾ"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"സ്ക്രീൻ റെക്കോർഡർ"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"സ്ക്രീൻ റെക്കോർഡിംഗ് പ്രോസസുചെയ്യുന്നു"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ഒരു സ്ക്രീൻ റെക്കോർഡിംഗ് സെഷനായി നിലവിലുള്ള അറിയിപ്പ്"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"സ്വയമേവ"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"ശബ്ദമോ വൈബ്രേഷനോ ഇല്ല"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ശബ്‌ദമോ വൈബ്രേഷനോ ഇല്ല, സംഭാഷണ വിഭാഗത്തിന് താഴെയായി ദൃശ്യമാകും"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ഫോൺ ക്രമീകരണം അടിസ്ഥാനമാക്കി റിംഗ്/വൈബ്രേറ്റ് ചെയ്യും"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ഫോൺ ക്രമീകരണം അടിസ്ഥാനമാക്കി റിംഗ്/വൈബ്രേറ്റ് ചെയ്‌തേക്കാം. <xliff:g id="APP_NAME">%1$s</xliff:g>-ൽ നിന്നുള്ള സംഭാഷണങ്ങൾ ഡിഫോൾട്ടായി ബബിൾ ചെയ്യുന്നു."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"ഉപകരണ ക്രമീകരണം അടിസ്ഥാനമാക്കി റിംഗ് ചെയ്യും അല്ലെങ്കിൽ വൈബ്രേറ്റ് ചെയ്യും"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ഉപകരണ ക്രമീകരണം അടിസ്ഥാനമാക്കി റിംഗ് ചെയ്യും അല്ലെങ്കിൽ വൈബ്രേറ്റ് ചെയ്യും. <xliff:g id="APP_NAME">%1$s</xliff:g> എന്ന ആപ്പിൽ നിന്നുള്ള സംഭാഷണങ്ങൾ ഡിഫോൾട്ടായി ബബിൾ ചെയ്യുന്നു."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ഈ അറിയിപ്പ് വരുമ്പോൾ ശബ്‌ദിക്കുകയാണോ വൈബ്രേറ്റ് ചെയ്യുകയാണോ വേണ്ടതെന്ന് നിർണ്ണയിക്കാൻ സിസ്‌റ്റത്തെ അനുവദിക്കുക"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;നില:&lt;/b&gt; ഡിഫോൾട്ടാക്കി പ്രമോട്ട് ചെയ്‌തു"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;നില:&lt;/b&gt; നിശബ്‌ദമാക്കി തരം താഴ്ത്തി"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"നിയന്ത്രണങ്ങൾ ചേർക്കാൻ ആപ്പ് തിരഞ്ഞെടുക്കുക"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# നിയന്ത്രണം ചേർത്തു.}other{# നിയന്ത്രണങ്ങൾ ചേർത്തു.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"നീക്കം ചെയ്‌തു"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> ചേർക്കണോ?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"നിങ്ങൾ <xliff:g id="APPNAME">%s</xliff:g> ചേർത്താൽ, അതിന് ഈ പാനലിലേക്ക് നിയന്ത്രണങ്ങളും ഉള്ളടക്കവും ചേർക്കാനാകും. ചില ആപ്പുകളിൽ, ഇവിടെ ഏത് നിയന്ത്രണങ്ങൾ ദൃശ്യമാകണമെന്ന് നിങ്ങൾക്ക് തിരഞ്ഞെടുക്കാനാകും."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"പ്രിയപ്പെട്ടതാക്കി"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"പ്രിയപ്പെട്ടതാക്കി, സ്ഥാനം <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"പ്രിയപ്പെട്ടതല്ലാതാക്കി"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%2$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"പഴയപടിയാക്കുക"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യാൻ അടുത്തേക്ക് നീക്കുക"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ഇവിടെ പ്ലേ ചെയ്യാൻ <xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിന് അടുത്തേക്ക് നീക്കുക"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ഇവിടെ പ്ലേ ചെയ്യാൻ <xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിനടുത്തേക്ക് നീക്കുക"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യുന്നു"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"എന്തോ കുഴപ്പമുണ്ടായി. വീണ്ടും ശ്രമിക്കുക."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"ലോഡ് ചെയ്യുന്നു"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ടാബ്‌ലെറ്റ്"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"നിഷ്‌ക്രിയം, ആപ്പ് പരിശോധിക്കൂ"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"കണ്ടെത്തിയില്ല"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"നിയന്ത്രണം ലഭ്യമല്ല"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"പിശക്, വീണ്ടും ശ്രമിക്കുക"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"നിയന്ത്രണങ്ങൾ ചേർക്കുക"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"നിയന്ത്രണങ്ങൾ എഡിറ്റ് ചെയ്യുക"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ആപ്പ് ചേർക്കുക"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ഔട്ട്പുട്ടുകൾ ചേർക്കുക"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ഗ്രൂപ്പ്"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"ഒരു ഉപകരണം തിരഞ്ഞെടുത്തു"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"വോളിയം"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"സ്‌പീക്കറുകളും ഡിസ്പ്ലേകളും"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"നിർദ്ദേശിച്ച ഉപകരണങ്ങൾ"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ബ്രോഡ്‌കാസ്‌റ്റ് എങ്ങനെയാണ് പ്രവർത്തിക്കുന്നത്"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ബ്രോഡ്‌കാസ്റ്റ്"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"അനുയോജ്യമായ Bluetooth ഉപകരണങ്ങളോടെ സമീപമുള്ള ആളുകൾക്ക് നിങ്ങൾ ബ്രോഡ്‌കാസ്‌റ്റ് ചെയ്യുന്ന മീഡിയ കേൾക്കാനാകും"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ഈ സ്ക്രീൻ ഓഫാകും"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം അൺഫോൾഡ് ആകുന്നു"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം, കറങ്ങുന്ന വിധത്തിൽ ഫ്ലിപ്പ് ആകുന്നു"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ബാറ്ററി ചാർജ് ശേഷിക്കുന്നു"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"നിങ്ങളുടെ സ്റ്റൈലസ് ചാർജറുമായി കണക്റ്റ് ചെയ്യുക"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"സ്റ്റൈലസിന്റെ ബാറ്ററി ചാർജ് കുറവാണ്"</string>
+    <string name="video_camera" msgid="7654002575156149298">"വീഡിയോ ക്യാമറ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"ഈ പ്രൊഫൈലിൽ നിന്ന് കോൾ ചെയ്യാനാകില്ല"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"ഔദ്യോഗിക പ്രൊഫൈലിൽ നിന്ന് മാത്രം ഫോൺ കോളുകൾ ചെയ്യാനാണ് നിങ്ങളുടെ ഔദ്യോഗിക നയം അനുവദിക്കുന്നത്"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"ഔദ്യോഗിക പ്രൊഫൈലിലേക്ക് മാറുക"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"അടയ്ക്കുക"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 08c47b0..f4ace18 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Доод талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Зүүн талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Баруун талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ажлын дэлгэцийн агшнуудыг <xliff:g id="APP">%1$s</xliff:g> апп дээр хадгалсан"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлс"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Дэлгэцийн үйлдэл бичигч"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Дэлгэц бичлэг боловсруулж байна"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Дэлгэц бичих горимын үргэлжилж буй мэдэгдэл"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Автомат"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Дуу эсвэл чичиргээ байхгүй"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Дуу эсвэл чичиргээ байхгүй бөгөөд харилцан ярианы хэсгийн доод талд харагдана"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Утасны тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичирхийлж болзошгүй"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Утасны тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичирхийлж болзошгүй. <xliff:g id="APP_NAME">%1$s</xliff:g>-н харилцан яриаг өгөгдмөл тохиргооны дагуу бөмбөлөг болгоно."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Төхөөрөмжийн тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичиргэж болзошгүй"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Төхөөрөмжийн тохиргоонд тулгуурлан хонх дуугаргах эсвэл чичиргэж болзошгүй. <xliff:g id="APP_NAME">%1$s</xliff:g>-н харилцан яриаг өгөгдмөлөөр бөмбөлөг болгоно."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Энэ мэдэгдэл дуу гаргах эсвэл чичрэх эсэхийг системээр тодорхойлуулаарай"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Төлөв:&lt;/b&gt; Өгөгдмөл болгож дэвшүүлсэн"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Төлөв:&lt;/b&gt; Чимээгүй болгож зэрэглэлийг нь бууруулсан"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Хяналтууд нэмэхийн тулд аппыг сонгоно уу"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# хяналт нэмсэн.}other{# хяналт нэмсэн.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Хассан"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Дуртай гэж тэмдэглэсэн"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"<xliff:g id="NUMBER">%d</xliff:g>-р байршилд дуртай гэж тэмдэглэсэн"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Дургүй гэж тэмдэглэсэн"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%2$s</xliff:g> дээр тоглуулах"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Болих"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулахын тулд төхөөрөмжөө ойртуулна уу"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Энд тоглуулахын тулд <xliff:g id="DEVICENAME">%1$s</xliff:g>-д ойртоно уу"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Энд тоглуулахын тулд <xliff:g id="DEVICENAME">%1$s</xliff:g> руу ойртуулна уу"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулж байна"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Алдаа гарлаа. Дахин оролдоно уу."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Ачаалж байна"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Идэвхгүй байна, аппыг шалгана уу"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Олдсонгүй"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Хяналт боломжгүй байна"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Алдаа гарав, дахин оролдоно уу"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Хяналт нэмэх"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Хяналтыг өөрчлөх"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Гаралт нэмэх"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Бүлэг"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 төхөөрөмж сонгосон"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Дууны түвшин"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Чанга яригч ба дэлгэц"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Санал болгосон төхөөрөмжүүд"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Нэвтрүүлэлт хэрхэн ажилладаг вэ?"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Нэвтрүүлэлт"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Тохиромжтой Bluetooth төхөөрөмжүүдтэй таны ойролцоох хүмүүс таны нэвтрүүлж буй медиаг сонсох боломжтой"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Энэ дэлгэц унтарна"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Эвхэгддэг төхөөрөмжийг дэлгэж байна"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Эвхэгддэг төхөөрөмжийг хөнтөрч байна"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> батарей үлдлээ"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Мэдрэгч үзгээ цэнэглэгчтэй холбоорой"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Мэдрэгч үзэгний батарей бага байна"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Видео камер"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Энэ профайлаас залгах боломжгүй"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Таны ажлын бодлого танд зөвхөн ажлын профайлаас утасны дуудлага хийхийг зөвшөөрдөг"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Ажлын профайл руу сэлгэх"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Хаах"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 103e8d2..96ecc99 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"खालील सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"डाव्या सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"उजव्या सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ऑफिससंबंधित स्क्रीनशॉट <xliff:g id="APP">%1$s</xliff:g> अ‍ॅपमध्ये सेव्ह केले आहेत"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"फाइल"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"स्क्रीन रेकॉर्डर"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"स्क्रीन रेकॉर्डिंग प्रोसेस सुरू"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रेकॉर्ड सत्रासाठी सुरू असलेली सूचना"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ऑटोमॅटिक"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"आवाज किंवा व्हायब्रेशन नाही"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"आवाज किंवा व्हायब्रेशन नाही आणि संभाषण विभागात सर्वात तळाशी दिसते"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"फोन सेटिंग्जनुसार फोन रिंग किंवा व्हायब्रेट होऊ शकतो"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"फोन सेटिंग्जच्या आधारावर रिंग किंवा व्हायब्रेट होऊ शकतो. <xliff:g id="APP_NAME">%1$s</xliff:g> मधील संभाषणे बाय डीफॉल्ट बबल होतात."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"डिव्‍हाइस सेटिंग्जनुसार रिंग किंवा व्हायब्रेट होऊ शकतो"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"डिव्‍हाइस सेटिंग्जनुसार रिंग किंवा व्हायब्रेट होऊ शकतो. <xliff:g id="APP_NAME">%1$s</xliff:g> मधील संभाषणे बाय डीफॉल्ट बबल होतात."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ही सूचना मिळाल्‍यावर आवाज व्‍हावा की व्हायब्रेशन व्‍हावे ते सिस्‍टममध्ये नमूद करा"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;स्थिती&lt;/b&gt; ही डीफॉल्ट म्हणून प्रमोट केली गेली"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;स्थिती&lt;/b&gt; ला सायलंट म्हणून डीमोट केले गेले"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"नियंत्रणे जोडण्यासाठी ॲप निवडा"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# नियंत्रण जोडले आहे.}other{# नियंत्रणे जोडली आहेत.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"काढून टाकले"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> जोडायचे आहे का?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"तुम्ही <xliff:g id="APPNAME">%s</xliff:g> जोडता, तेव्हा ते या पॅनलमध्ये नियंत्रणे आणि आशय जोडू शकते. येथे कोणती नियंत्रणे दाखवावीत ते तुम्ही काही अ‍ॅप्समध्ये निवडू शकता."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"आवडले"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"आवडले, स्थान <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"नावडले"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> मध्ये <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"पहिल्यासारखे करा"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले करण्यासाठी जवळ जा"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"येथे प्ले करण्यासाठी <xliff:g id="DEVICENAME">%1$s</xliff:g> च्या जवळ जा"</string>
-    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले केला जात आहे"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"येथे प्ले करण्यासाठी, <xliff:g id="DEVICENAME">%1$s</xliff:g> च्या जवळ जा"</string>
+    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले होत आहे"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"काहीतरी चूक झाली. पुन्हा प्रयत्न करा."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"लोड करत आहे"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"टॅबलेट"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय, ॲप तपासा"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"आढळले नाही"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"नियंत्रण उपलब्ध नाही"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"एरर, पुन्हा प्रयत्न करा"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"नियंत्रणे जोडा"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"नियंत्रणे संपादित करा"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"अ‍ॅप जोडा"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट जोडा"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"गट"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिव्हाइस निवडले"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"व्हॉल्यूम"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"स्पीकर आणि डिस्प्ले"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"सुचवलेली डिव्हाइस"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ब्रॉडकास्टिंग कसे काम करते"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ब्रॉडकास्ट करा"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"कंपॅटिबल ब्लूटूथ डिव्‍हाइस असलेले तुमच्या जवळपासचे लोक हे तुम्ही ब्रॉडकास्ट करत असलेला मीडिया ऐकू शकतात"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ही स्क्रीन बंद होईल"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड करता येण्यासारखे डिव्हाइस अनफोल्ड केले जात आहे"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड करता येण्यासारखे डिव्हाइस आजूबाजूला फ्लिप केले जात आहे"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> बॅटरी शिल्लक आहे"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"तुमचे स्टायलस चार्जरशी कनेक्ट करा"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"स्टायलस बॅटरी कमी आहे"</string>
+    <string name="video_camera" msgid="7654002575156149298">"व्हिडिओ कॅमेरा"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"या प्रोफाइलवरून कॉल करू शकत नाही"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"तुमचे कामाशी संबंधित धोरण तुम्हाला फक्त कार्य प्रोफाइलवरून फोन कॉल करन्याची अनुमती देते"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"कार्य प्रोफाइलवर स्विच करा"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"बंद करा"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index b0b4096..2782925 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Sempadan bawah <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sempadan kiri <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sempadan kanan <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Tangkapan skrin tugasan disimpan dalam apl <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fail"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Perakam Skrin"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Memproses rakaman skrin"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Pemberitahuan breterusan untuk sesi rakaman skrin"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatik"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Tiada bunyi atau getaran"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Tiada bunyi atau getaran dan muncul di sebelah bawah dalam bahagian perbualan"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Mungkin berbunyi atau bergetar berdasarkan tetapan telefon"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Mungkin berbunyi atau bergetar berdasarkan tetapan telefon. Perbualan daripada gelembung <xliff:g id="APP_NAME">%1$s</xliff:g> secara lalai."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Mungkin berbunyi atau bergetar berdasarkan tetapan peranti"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Mungkin berbunyi atau bergetar berdasarkan tetapan peranti. Perbualan daripada gelembung <xliff:g id="APP_NAME">%1$s</xliff:g> secara lalai."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Minta sistem menentukan jika pemberitahuan ini patut menghasilkan bunyi atau getaran"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Dinaikkan Taraf kepada Lalai"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Diturunkan Taraf kepada Senyap"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Pilih apl untuk menambahkan kawalan"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kawalan ditambah.}other{# kawalan ditambah.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Dialih keluar"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Digemari"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Digemari, kedudukan <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Dinyahgemari"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> daripada <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Buat asal"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Alihkan lebih dekat untuk bermain pada<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Dekatkan dengan <xliff:g id="DEVICENAME">%1$s</xliff:g> untuk bermain di sini"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Untuk bermain di sini, bergerak lebih dekat kepada <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Dimainkan pada <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Kesilapan telah berlaku. Cuba lagi."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Memuatkan"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Tidak aktif, semak apl"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kawalan tidak tersedia"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Ralat, cuba lagi"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Tambah kawalan"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edit kawalan"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tambah output"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Kumpulan"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 peranti dipilih"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Kelantangan"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Pembesar Suara &amp; Paparan"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Peranti yang Dicadangkan"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cara siaran berfungsi"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Siarkan"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Orang berdekatan anda dengan peranti Bluetooth yang serasi boleh mendengar media yang sedang anda siarkan"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Skrin ini akan dimatikan"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Peranti boleh lipat dibuka"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Peranti boleh lipat diterbalikkan"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateri tinggal <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Sambungkan stilus anda kepada pengecas"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateri stilus lemah"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Kamera video"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Tidak dapat membuat panggilan daripada profil ini"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Dasar kerja anda membenarkan anda membuat panggilan telefon hanya daripada profil kerja"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Tukar kepada profil kerja"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Tutup"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 2014f5c..f875654 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"အောက်ခြေအနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ဘယ်ဘက်အနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ညာဘက်အနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"အလုပ်နှင့်ဆိုင်သော ဖန်သားပြင်ဓာတ်ပုံများကို <xliff:g id="APP">%1$s</xliff:g> အက်ပ်တွင် သိမ်းသည်"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ဖိုင်များ"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"ဖန်သားပြင် ရိုက်ကူးမှု"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"စကရင်ရိုက်ကူးမှု အပြီးသတ်နေသည်"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ဖန်သားပြင် ရိုက်ကူးသည့် စက်ရှင်အတွက် ဆက်တိုက်လာနေသော အကြောင်းကြားချက်"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"အလိုအလျောက်"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"အသံ သို့မဟုတ် တုန်ခါမှုမရှိပါ"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"အသံ သို့မဟုတ် တုန်ခါမှုမရှိပါ၊ စကားဝိုင်းကဏ္ဍ၏ အောက်ပိုင်းတွင် မြင်ရသည်"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ဖုန်းဆက်တင်များပေါ် အခြေခံပြီး အသံမြည်နိုင်သည် သို့မဟုတ် တုန်ခါနိုင်သည်"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ဖုန်းဆက်တင်ပေါ် အခြေခံပြီး အသံမြည် (သို့) တုန်ခါနိုင်သည်။ <xliff:g id="APP_NAME">%1$s</xliff:g> မှ စကားဝိုင်းများကို ပူဖောင်းကွက်ဖြင့် အလိုအလျောက်ပြသည်။"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"စက်ပစ္စည်း ဆက်တင်များပေါ် အခြေခံပြီး အသံမြည်နိုင်သည် (သို့) တုန်ခါနိုင်သည်"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"စက်ပစ္စည်းဆက်တင်များပေါ် အခြေခံပြီး အသံမြည်နိုင်သည် (သို့) တုန်ခါနိုင်သည်။ မူရင်းသတ်မှတ်ချက်အဖြစ် <xliff:g id="APP_NAME">%1$s</xliff:g> မှ စကားဝိုင်းများကို ပူဖောင်းကွက်ဖြင့် ပြသည်။"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ဤအကြောင်းကြားချက်က အသံ သို့မဟုတ် တုန်ခါမှု ပေးရန် သင့်/မသင့်ကို စနစ်က ဆုံးဖြတ်ပါစေ"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;အခြေအနေ-&lt;/b&gt; မူရင်းသို့ ချိန်ညှိထားသည်"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;အခြေအနေ-&lt;/b&gt; အသံတိတ်ခြင်းသို့ ပြန်ချိန်ညှိထားသည်"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"ထိန်းချုပ်မှုများထည့်ရန် အက်ပ်ရွေးခြင်း"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{ထိန်းချုပ်ခလုတ် # ခု ထည့်ထားသည်။}other{ထိန်းချုပ်ခလုတ် # ခု ထည့်ထားသည်။}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"ဖယ်ရှားထားသည်"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"အကြိုက်ဆုံးတွင် ထည့်ထားသည်"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"အကြိုက်ဆုံးတွင် ထည့်ထားသည်၊ အဆင့် <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"အကြိုက်ဆုံးမှ ဖယ်ရှားထားသည်"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%2$s</xliff:g> တွင် ဖွင့်ပါ"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"နောက်ပြန်ရန်"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင်ဖွင့်ရန် အနီးသို့ရွှေ့ပါ"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ဤနေရာတွင်ဖွင့်ရန် <xliff:g id="DEVICENAME">%1$s</xliff:g> အနီးသို့တိုးပါ"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ဤနေရာတွင် ဖွင့်ရန် <xliff:g id="DEVICENAME">%1$s</xliff:g> အနီးသို့ ရွှေ့ပါ"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင် ဖွင့်နေသည်"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"တစ်ခုခုမှားသွားသည်။ ထပ်စမ်းကြည့်ပါ။"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"ဖွင့်နေသည်"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"တက်ဘလက်"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"မတွေ့ပါ"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"ထိန်းချုပ်မှု မရနိုင်ပါ"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"မှားသွားသည်၊ ပြန်စမ်းကြည့်ပါ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ထိန်းချုပ်မှုများ ထည့်ရန်"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ထိန်းချုပ်မှုများ ပြင်ရန်"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"မီဒီယာအထွက်များ ထည့်ရန်"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"အုပ်စု"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"စက်ပစ္စည်း ၁ ခုကို ရွေးချယ်ထားသည်"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"အသံအတိုးအကျယ်"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"စပီကာနှင့် ဖန်သားပြင်များ"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"အကြံပြုထားသော စက်ပစ္စည်းများ"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ထုတ်လွှင့်မှုဆောင်ရွက်ပုံ"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ထုတ်လွှင့်ခြင်း"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"အနီးရှိတွဲသုံးနိုင်သော ဘလူးတုသ်သုံးစက် အသုံးပြုသူများက သင်ထုတ်လွှင့်နေသော မီဒီယာကို နားဆင်နိုင်သည်"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ဤဖန်သားပြင်ကို ပိတ်လိုက်မည်"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ခေါက်နိုင်သောစက်ကို ဖြန့်လိုက်သည်"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ခေါက်နိုင်သောစက်ကို တစ်ဘက်သို့ လှန်လိုက်သည်"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ဘက်ထရီ <xliff:g id="PERCENTAGE">%s</xliff:g> ကျန်သေးသည်"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"စတိုင်လပ်စ်ကို အားသွင်းကိရိယာနှင့် ချိတ်ဆက်ခြင်း"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"စတိုင်လပ်စ် ဘက်ထရီ အားနည်းနေသည်"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ဗီဒီယိုကင်မရာ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"ဤပရိုဖိုင်မှ ခေါ်ဆို၍ မရပါ"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"သင့်အလုပ်မူဝါဒသည် သင့်အား အလုပ်ပရိုဖိုင်မှသာ ဖုန်းခေါ်ဆိုခွင့် ပြုသည်"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"အလုပ်ပရိုဖိုင်သို့ ပြောင်းရန်"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"ပိတ်ရန်"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 2b1ebfd..5c1258b 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Nedre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Høyre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Jobbrelaterte skjermdumper lagres i <xliff:g id="APP">%1$s</xliff:g>-appen"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Skjermopptaker"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Behandler skjermopptaket"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Vedvarende varsel for et skjermopptak"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatisk"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Ingen lyd eller vibrering"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ingen lyd eller vibrering, og vises lavere i samtaledelen"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan ringe eller vibrere basert på telefoninnstillingene"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan ringe eller vibrere basert på telefoninnstillingene. Samtaler fra <xliff:g id="APP_NAME">%1$s</xliff:g> lager bobler som standard."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan ringe eller vibrere basert på enhetsinnstillingene"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan ringe eller vibrere basert på enhetsinnstillingene. Samtaler fra <xliff:g id="APP_NAME">%1$s</xliff:g> vises som standard som bobler."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"La systemet velge om dette varselet skal lage lyd eller vibrere"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Oppgradert til standard"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Nedgradert til lydløst"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Velg en app for å legge til kontroller"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontroll er lagt til.}other{# kontroller er lagt til.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Fjernet"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favoritt"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favoritt, posisjon <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Fjernet som favoritt"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> fra <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Angre"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flytt nærmere for å spille av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Flytt deg nærmere <xliff:g id="DEVICENAME">%1$s</xliff:g> for å spille av her"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"For å spille av her, gå nærmere <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Spilles av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Noe gikk galt. Prøv på nytt."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Laster inn"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"nettbrett"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Sjekk appen"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Ikke funnet"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrollen er utilgjengelig"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"En feil oppsto. Prøv på nytt"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Legg til kontroller"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Endre kontroller"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Legg til utenheter"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet er valgt"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volum"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Høyttalere og skjermer"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Foreslåtte enheter"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Slik fungerer kringkasting"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Kringkasting"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Folk i nærheten med kompatible Bluetooth-enheter kan lytte til mediene du kringkaster"</string>
@@ -1010,10 +1019,19 @@
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Avbryt"</string>
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Vend nå"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Brett ut telefonen for å ta bedre selfier"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vil du bytte til frontskjermen for bedre selfier?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vil du bruke frontkameraet for bedre selfier?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Bruk det bakovervendte kameraet for å ta bredere bilder med høyere oppløsning."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Denne skjermen slås av"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En sammenleggbar enhet blir brettet ut"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En sammenleggbar enhet blir snudd"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteri gjenstår"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Koble pekepennen til en lader"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Det er lite batteri i pekepennen"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Kan ikke ringe fra denne profilen"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Som følge av jobbreglene dine kan du bare starte telefonanrop fra jobbprofilen."</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Bytt til jobbprofilen"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Lukk"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 98fe304..e2c8b4b 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"फेदबाट <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"बायाँ किनाराबाट <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"दायाँ किनाराबाट <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"कार्य प्रोफाइल प्रयोग गरी लिइएका स्क्रिनसटहरू <xliff:g id="APP">%1$s</xliff:g> एपमा सेभ गरिन्छन्"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"स्क्रिन रेकर्डर"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"स्क्रिन रेकर्डिङको प्रक्रिया अघि बढाइँदै"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"कुनै स्क्रिन रेकर्ड गर्ने सत्रका लागि चलिरहेको सूचना"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"स्वचालित"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"बज्दैन पनि, भाइब्रेट पनि हुँदैन"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"बज्दैन पनि, भाइब्रेट पनि हुँदैन र वार्तालाप खण्डको तलतिर देखा पर्छ"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"फोनको सेटिङका आधारमा घन्टी बज्न वा भाइब्रेट हुन सक्छ"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"फोनको सेटिङका आधारमा घन्टी बज्न वा भाइब्रेट हुन सक्छ। <xliff:g id="APP_NAME">%1$s</xliff:g> का वार्तालापहरू डिफल्ट रूपमा बबलमा देखाइन्छन्।"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"डिभाइसको सेटिङका आधारमा घन्टी बज्न वा भाइब्रेट हुन सक्छ"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"डिभाइसको सेटिङका आधारमा घन्टी बज्न वा कम्पन हुन सक्छ। <xliff:g id="APP_NAME">%1$s</xliff:g> मार्फत गरिएका वार्तालापहरू स्वतः बबलमा देखिन्छन्।"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"सिस्टमलाई यो सूचना आउँदा ध्वनि बज्नु पर्छ वा कम्पन हुनु पर्छ भन्ने कुराको निधो गर्न दिनुहोस्"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;स्थिति:&lt;/b&gt; सूचनालाई महत्त्वपूर्ण ठानी डिफल्ट मोडमा सेट गरिएको छ"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;स्थिति:&lt;/b&gt; सूचनालाई कम महत्त्वपूर्ण ठानी साइलेन्ट मोडमा सेट गरिएको छ"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"कन्ट्रोल थप्नु पर्ने एप छान्नुहोस्"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# कन्ट्रोल हालियो।}other{# वटा कन्ट्रोल हालियो।}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"हटाइएको"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> हाल्ने हो?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"तपाईंले <xliff:g id="APPNAME">%s</xliff:g> हाल्नुभयो भने यसले यो प्यानलमा सेटिङ र सामग्री हाल्न सक्छ। तपाईं केही एपहरूमा यहाँ कुन कुन सेटिङ देखाउने भन्ने कुरा छनौट गर्न सक्नुहुन्छ।"</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"मनपराइएको"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"मन पराइएका कुराहरूको <xliff:g id="NUMBER">%d</xliff:g> औँ स्थानमा"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"मन पर्ने कुराहरूको सूचीमा नराखिएको"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%2$s</xliff:g> मा बजाउनुहोस्"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"अन्डू गर्नुहोस्"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गर्न आफ्नो डिभाइस नजिकै लैजानुहोस्"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"तपाईं यहाँ प्ले गर्न चाहनुहुन्छ भने आफ्नो डिभाइसलाई <xliff:g id="DEVICENAME">%1$s</xliff:g> नजिकै लैजानुहोस्"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"यो डिभाइसमा प्ले गर्न <xliff:g id="DEVICENAME">%1$s</xliff:g> को अझ नजिक जानुहोस्"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गरिँदै छ"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"केही चिज गडबड भयो। फेरि प्रयास गर्नुहोस्।"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"लोड हुँदै छ"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ट्याब्लेट"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"फेला परेन"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"नियन्त्रण उपलब्ध छैन"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"त्रुटि भयो, फेरि प्रयास गर्नु…"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"कन्ट्रोल थप्नुहोस्"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"कन्ट्रोल सम्पादन गर्नुहोस्"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"एप हाल्नुहोस्"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट यन्त्रहरू थप्नुहोस्"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"समूह"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"१ यन्त्र चयन गरियो"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"भोल्युम"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"स्पिकर तथा डिस्प्लेहरू"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"सिफारिस गरिएका डिभाइसहरू"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"प्रसारण गर्ने सुविधाले कसरी काम गर्छ"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"प्रसारण"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"कम्प्याटिबल ब्लुटुथ डिभाइस भएका नजिकैका मान्छेहरू तपाईंले प्रसारण गरिरहनुभएको मिडिया सुन्न सक्छन्"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ यो स्क्रिन अफ हुने छ"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड गर्न मिल्ने डिभाइस अनफोल्ड गरेको देखाइएको एनिमेसन"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड गर्न मिल्ने डिभाइस यताउता पल्टाएर देखाइएको एनिमेसन"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ब्याट्री बाँकी छ"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"आफ्नो स्टाइलस चार्जरमा कनेक्ट गर्नुहोस्"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"स्टाइलसको ब्याट्री लो छ"</string>
+    <string name="video_camera" msgid="7654002575156149298">"भिडियो क्यामेरा"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"यो प्रोफाइलबाट कल गर्न सकिँदैन"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"तपाईंको कामसम्बन्धी नीतिअनुसार कार्य प्रोफाइलबाट मात्र फोन कल गर्न सकिन्छ"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"कार्य प्रोफाइल प्रयोग गर्नुहोस्"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"बन्द गर्नुहोस्"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index e49b9fa..56d5303 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ondergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linkergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechtergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Werkscreenshots worden opgeslagen in de <xliff:g id="APP">%1$s</xliff:g>-app"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Bestanden"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Schermopname"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Schermopname verwerken"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Doorlopende melding voor een schermopname-sessie"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatisch"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Geen geluid of trilling"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Geen geluid of trilling en wordt lager in het gedeelte met gesprekken getoond"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan overgaan of trillen op basis van de telefooninstellingen"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan overgaan of trillen op basis van de telefooninstellingen. Gesprekken uit <xliff:g id="APP_NAME">%1$s</xliff:g> worden standaard als bubbels getoond."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan overgaan of trillen op basis van de apparaatinstellingen"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan overgaan of trillen op basis van de apparaatinstellingen. Gesprekken uit <xliff:g id="APP_NAME">%1$s</xliff:g> worden standaard als bubbels weergegeven."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Het systeem laten bepalen of deze melding geluid moet maken of moet trillen"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; opgeschaald naar Standaard"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; verlaagd naar Stil"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Kies de app waaraan je bedieningselementen wilt toevoegen"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# bedieningselement toegevoegd.}other{# bedieningselementen toegevoegd.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Verwijderd"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> toevoegen?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Als je <xliff:g id="APPNAME">%s</xliff:g> toevoegt, kan deze app bedieningselementen en content aan dit deelvenster toevoegen. In sommige apps kun je kiezen welke bedieningselementen hier worden getoond."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Gemarkeerd als favoriet"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Gemarkeerd als favoriet, positie <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Verwijderd als favoriet"</string>
@@ -849,12 +853,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Ongedaan maken"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ga dichter naar <xliff:g id="DEVICENAME">%1$s</xliff:g> toe om af te spelen"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Ga dichter bij <xliff:g id="DEVICENAME">%1$s</xliff:g> staan om hier af te spelen"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Houd dichter bij <xliff:g id="DEVICENAME">%1$s</xliff:g> om af te spelen"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ga dichter naar <xliff:g id="DEVICENAME">%1$s</xliff:g> toe om hier af te spelen"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Afspelen op <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Er is iets misgegaan. Probeer het opnieuw."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Laden"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactief, check de app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Niet gevonden"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Beheeroptie niet beschikbaar"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Fout, probeer het opnieuw"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Bedieningselementen toevoegen"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Bedieningselementen bewerken"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"App toevoegen"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Uitvoer toevoegen"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Groep"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Eén apparaat geselecteerd"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers en schermen"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Voorgestelde apparaten"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Hoe uitzenden werkt"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Uitzending"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Mensen bij jou in de buurt met geschikte bluetooth-apparaten kunnen luisteren naar de media die je uitzendt"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Dit scherm gaat uit"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Opvouwbaar apparaat wordt uitgevouwen"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Opvouwbaar apparaat wordt gedraaid"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Nog <xliff:g id="PERCENTAGE">%s</xliff:g> batterijlading"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Verbind je stylus met een oplader"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Batterij van stylus bijna leeg"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videocamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Je kunt niet bellen vanuit dit profiel"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Op basis van je werkbeleid kun je alleen bellen vanuit het werkprofiel"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Overschakelen naar werkprofiel"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Sluiten"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 71d0822..5396ede 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ନିମ୍ନ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ବାମ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ଡାହାଣ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ୱାର୍କ ସ୍କ୍ରିନସଟଗୁଡ଼ିକୁ <xliff:g id="APP">%1$s</xliff:g> ଆପରେ ସେଭ କରାଯାଇଛି"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ଫାଇଲଗୁଡ଼ିକ"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"ସ୍କ୍ରିନ୍ ରେକର୍ଡର୍"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ସ୍କ୍ରିନ ରେକର୍ଡିଂର ପ୍ରକ୍ରିୟାକରଣ"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ଏକ ସ୍କ୍ରି‍ନ୍‍ ରେକର୍ଡ୍‍ ସେସନ୍‍ ପାଇଁ ଚାଲୁଥିବା ବିଜ୍ଞପ୍ତି"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ସ୍ୱଚାଳିତ"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"କୌଣସି ସାଉଣ୍ଡ କିମ୍ବା ଭାଇବ୍ରେସନ୍ ନାହିଁ"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"କୌଣସି ସାଉଣ୍ଡ କିମ୍ବା ଭାଇବ୍ରେସନ୍ ନାହିଁ ଏବଂ ବାର୍ତ୍ତାଳାପ ବିଭାଗର ନିମ୍ନରେ ଦେଖାଯାଏ"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ଫୋନ ସେଟିଂସ ଆଧାରରେ ରିଙ୍ଗ କିମ୍ବା ଭାଇବ୍ରେଟ ହୋଇପାରେ"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ଫୋନ୍ ସେଟିଂସ୍ ଆଧାରରେ ରିଙ୍ଗ କିମ୍ବା ଭାଇବ୍ରେଟ୍ ହୋଇପାରେ। <xliff:g id="APP_NAME">%1$s</xliff:g>ରୁ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ଡିଫଲ୍ଟ ଭାବରେ ବବଲ୍ ହୁଏ।"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"ଡିଭାଇସ ସେଟିଂସ ଆଧାରରେ ରିଂ କିମ୍ବା ଭାଇବ୍ରେଟ ହୋଇପାରେ"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ଡିଭାଇସ ସେଟିଂସ ଆଧାରରେ ରିଂ କିମ୍ବା ଭାଇବ୍ରେଟ ହୋଇପାରେ। ଡିଫଲ୍ଟ ଭାବରେ <xliff:g id="APP_NAME">%1$s</xliff:g>ରୁ ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ ବବଲ ଭାବେ ଦେଖାଯାଏ।"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ଏହି ବିଜ୍ଞପ୍ତି ପ୍ରାପ୍ତ ହେବା ସମୟରେ ସାଉଣ୍ଡ ହେବା ଉଚିତ ନା ଭାଇବ୍ରେସନ୍ ତାହା ସିଷ୍ଟମକୁ ସ୍ଥିର କରିବାକୁ ଦିଅନ୍ତୁ"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;ସ୍ଥିତି:&lt;/b&gt; ଡିଫଲ୍ଟକୁ ପ୍ରମୋଟ୍ କରାଯାଇଛି"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;ସ୍ଥିତି:&lt;/b&gt; ନୀରବକୁ ଡିମୋଟ୍ କରାଯାଇଛି"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଯୋଗ କରିବାକୁ ଆପ୍ ବାଛନ୍ତୁ"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{#ଟି ନିୟନ୍ତ୍ରଣ ଯୋଗ କରାଯାଇଛି।}other{#ଟି ନିୟନ୍ତ୍ରଣ ଯୋଗ କରାଯାଇଛି।}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"କାଢ଼ି ଦିଆଯାଇଛି"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"ପସନ୍ଦ କରାଯାଇଛି"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ପସନ୍ଦ କରାଯାଇଛି, ସ୍ଥିତି <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ନାପସନ୍ଦ କରାଯାଇଛି"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ରୁ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ଚଲାଇବା ପାଇଁ ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ଏଠାରେ ଚଲାଇବା ପାଇଁ <xliff:g id="DEVICENAME">%1$s</xliff:g>ର ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string>
-    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ଚାଲୁଛି"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ପ୍ଲେ କରିବା ପାଇଁ ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ଏଠାରେ ପ୍ଲେ କରିବା ପାଇଁ <xliff:g id="DEVICENAME">%1$s</xliff:g> ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string>
+    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ପ୍ଲେ ହେଉଛି"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"କିଛି ତ୍ରୁଟି ହୋଇଛି। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"ଲୋଡ ହେଉଛି"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ଟାବଲେଟ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"ମିଳିଲା ନାହିଁ"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"ନିୟନ୍ତ୍ରଣ ଉପଲବ୍ଧ ନାହିଁ"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"ତ୍ରୁଟି ହୋଇଛି, ପୁଣି ଚେଷ୍ଟା କର"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଏଡିଟ କରନ୍ତୁ"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ଆଉଟପୁଟ୍ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ଗୋଷ୍ଠୀ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1ଟି ଡିଭାଇସ୍ ଚୟନ କରାଯାଇଛି"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ଭଲ୍ୟୁମ"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ସ୍ପିକର ଏବଂ ଡିସପ୍ଲେଗୁଡ଼ିକ"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ପ୍ରସ୍ତାବିତ ଡିଭାଇସଗୁଡ଼ିକ"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ବ୍ରଡକାଷ୍ଟିଂ କିପରି କାମ କରେ"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ବ୍ରଡକାଷ୍ଟ"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ଆପଣଙ୍କ ଆଖପାଖର କମ୍ପାଟିବଲ ବ୍ଲୁଟୁଥ ଡିଭାଇସ ଥିବା ଲୋକମାନେ ଆପଣ ବ୍ରଡକାଷ୍ଟ କରୁଥିବା ମିଡିଆ ଶୁଣିପାରିବେ"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ଏହି ସ୍କ୍ରିନ ବନ୍ଦ ହୋଇଯିବ"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଅନଫୋଲ୍ଡ କରାଯାଉଛି"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଫ୍ଲିପ କରାଯାଉଛି"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ବେଟେରୀ ଚାର୍ଜ ବାକି ଅଛି"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ଏକ ଚାର୍ଜର ସହ ଆପଣଙ୍କ ଷ୍ଟାଇଲସକୁ କନେକ୍ଟ କରନ୍ତୁ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ଷ୍ଟାଇଲସ ବେଟେରୀର ଚାର୍ଜ କମ ଅଛି"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ଭିଡିଓ କେମେରା"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"ଏହି ପ୍ରୋଫାଇଲରୁ କଲ କରାଯାଇପାରିବ ନାହିଁ"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"ଆପଣଙ୍କ ୱାର୍କ ନୀତି ଆପଣଙ୍କୁ କେବଳ ୱାର୍କ ପ୍ରୋଫାଇଲରୁ ଫୋନ କଲ କରିବାକୁ ଅନୁମତି ଦିଏ"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"ୱାର୍କ ପ୍ରୋଫାଇଲକୁ ସ୍ୱିଚ କରନ୍ତୁ"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"ବନ୍ଦ କରନ୍ତୁ"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index f6ad3ad0..10c89f2 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ਹੇਠਾਂ ਦੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ਖੱਬੇ ਪਾਸੇ ਵਾਲੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ਸੱਜੇ ਪਾਸੇ ਵਾਲੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ਕਾਰਜ ਸਕ੍ਰੀਨਸ਼ਾਟਾਂ ਨੂੰ <xliff:g id="APP">%1$s</xliff:g> ਐਪ ਵਿੱਚ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ਫ਼ਾਈਲਾਂ"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਰ"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਜਾਰੀ ਹੈ"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"ਕਿਸੇ ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਸੈਸ਼ਨ ਲਈ ਚੱਲ ਰਹੀ ਸੂਚਨਾ"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ਸਵੈਚਲਿਤ"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"ਕੋਈ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਹੀਂ"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ਕੋਈ ਧੁਨੀ ਜਾਂ ਥਰਥਰਾਹਟ ਨਹੀਂ ਅਤੇ ਸੂਚਨਾਵਾਂ ਗੱਲਬਾਤ ਸੈਕਸ਼ਨ ਵਿੱਚ ਹੇਠਲੇ ਪਾਸੇ ਦਿਸਦੀਆਂ ਹਨ"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ਫ਼ੋਨ ਸੈਟਿੰਗਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਘੰਟੀ ਵੱਜ ਸਕਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਹੋ ਸਕਦੀ ਹੈ"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ਫ਼ੋਨ ਸੈਟਿੰਗਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਘੰਟੀ ਵੱਜ ਸਕਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਹੋ ਸਕਦੀ ਹੈ। ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਤੌਰ \'ਤੇ <xliff:g id="APP_NAME">%1$s</xliff:g> ਤੋਂ ਗੱਲਾਂਬਾਤਾਂ ਬਬਲ ਵਜੋਂ ਦਿਸਦੀਆਂ ਹਨ।"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"ਡੀਵਾਈਸ ਸੈਟਿੰਗਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਘੰਟੀ ਵੱਜ ਸਕਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਹੋ ਸਕਦੀ ਹੈ"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"ਡੀਵਾਈਸ ਸੈਟਿੰਗਾਂ ਦੇ ਆਧਾਰ \'ਤੇ ਘੰਟੀ ਵੱਜ ਸਕਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਹੋ ਸਕਦੀ ਹੈ। ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਤੌਰ \'ਤੇ <xliff:g id="APP_NAME">%1$s</xliff:g> ਬਬਲ ਤੋਂ ਗੱਲਾਂਬਾਤਾਂ।"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ਸਿਸਟਮ ਨੂੰ ਨਿਰਧਾਰਤ ਕਰਨ ਦਿਓ ਕਿ ਇਸ ਸੂਚਨਾ ਲਈ ਕੋਈ ਧੁਨੀ ਵਜਾਉਣੀ ਚਾਹੀਦੀ ਹੈ ਜਾਂ ਥਰਥਰਾਹਟ ਕਰਨੀ ਚਾਹੀਦੀ ਹੈ"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;ਸਥਿਤੀ:&lt;/b&gt; ਦਰਜਾ ਵਧਾ ਕੇ ਪੂਰਵ-ਨਿਰਧਾਰਤ \'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;ਸਥਿਤੀ:&lt;/b&gt; ਦਰਜਾ ਘਟਾ ਕੇ ਸ਼ਾਂਤ \'ਤੇ ਸੈੱਟ ਕੀਤਾ ਗਿਆ"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਐਪ ਚੁਣੋ"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ।}one{# ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ।}other{# ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕੀਤੇ ਗਏ।}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"ਹਟਾਇਆ ਗਿਆ"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"ਮਨਪਸੰਦ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ਮਨਪਸੰਦ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ, ਸਥਾਨ <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ਮਨਪਸੰਦ ਵਿੱਚੋਂ ਹਟਾਇਆ ਗਿਆ"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ਤੋਂ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"ਅਣਕੀਤਾ ਕਰੋ"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਉਣ ਲਈ ਨੇੜੇ ਲਿਜਾਓ"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ਇੱਥੇ ਚਲਾਉਣ ਲਈ <xliff:g id="DEVICENAME">%1$s</xliff:g> ਦੇ ਨੇੜੇ ਜਾਓ"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ਇੱਥੇ ਚਲਾਉਣ ਲਈ, <xliff:g id="DEVICENAME">%1$s</xliff:g> ਦੇ ਨੇੜੇ ਲਿਆਓ"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"ਕੋਈ ਗੜਬੜ ਹੋ ਗਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"ਲੋਡ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ਟੈਬਲੈੱਟ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"ਨਹੀਂ ਮਿਲਿਆ"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"ਕੰਟਰੋਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"ਗੜਬੜ, ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"ਕੰਟਰੋਲਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ਆਊਟਪੁੱਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"ਗਰੁੱਪ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ਡੀਵਾਈਸ ਨੂੰ ਚੁਣਿਆ ਗਿਆ"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ਅਵਾਜ਼"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ਸਪੀਕਰ ਅਤੇ ਡਿਸਪਲੇਆਂ"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ਸੁਝਾਏ ਗਏ ਡੀਵਾਈਸ"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ਪ੍ਰਸਾਰਨ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ਪ੍ਰਸਾਰਨ"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ਅਨੁਰੂਪ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸਾਂ ਨਾਲ ਨਜ਼ਦੀਕੀ ਲੋਕ ਤੁਹਾਡੇ ਵੱਲੋਂ ਪ੍ਰਸਾਰਨ ਕੀਤੇ ਜਾ ਰਹੇ ਮੀਡੀਆ ਨੂੰ ਸੁਣ ਸਕਦੇ ਹਨ"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ਇਹ ਸਕ੍ਰੀਨ ਬੰਦ ਹੋ ਜਾਵੇਗੀ"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਖੋਲ੍ਹਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਆਲੇ-ਦੁਆਲੇ ਫਲਿੱਪ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ਬੈਟਰੀ ਬਾਕੀ"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ਆਪਣੇ ਸਟਾਈਲਸ ਨੂੰ ਚਾਰਜਰ ਨਾਲ ਕਨੈਕਟ ਕਰੋ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ਸਟਾਈਲਸ ਦੀ ਬੈਟਰੀ ਘੱਟ ਹੈ"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ਵੀਡੀਓ ਕੈਮਰਾ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"ਇਸ ਪ੍ਰੋਫਾਈਲ ਤੋਂ ਕਾਲ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"ਤੁਹਾਡੀ ਕਾਰਜ ਨੀਤੀ ਤੁਹਾਨੂੰ ਸਿਰਫ਼ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਤੋਂ ਹੀ ਫ਼ੋਨ ਕਾਲਾਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਜਾਓ"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"ਬੰਦ ਕਰੋ"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 7cced9e..4abddab 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Przycięcie dolnej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Przycięcie lewej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Przycięcie prawej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Służbowe zrzuty ekranu są zapisywane w aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Pliki"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Nagrywanie ekranu"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Przetwarzam nagrywanie ekranu"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Stałe powiadomienie o sesji rejestrowania zawartości ekranu"</string>
@@ -377,7 +379,7 @@
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Dalej"</string>
-    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Udostępnianie i nagrywanie za pomocą aplikacji"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Udostępnianie i nagrywanie aplikacji"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Zezwolić tej aplikacji na udostępnianie lub nagrywanie?"</string>
     <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Podczas udostępniania, nagrywania lub przesyłania treści ta aplikacja ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
     <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Podczas udostępniania, nagrywania lub przesyłania treści ta aplikacja ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatycznie"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Bez dźwięku i wibracji"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Brak dźwięku i wibracji, wyświetla się niżej w sekcji rozmów"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Może włączać dzwonek lub wibracje w zależności od ustawień telefonu"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Może włączać dzwonek lub wibracje w zależności od ustawień telefonu. Rozmowy z aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g> są domyślnie wyświetlane jako dymki."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Może włączać dzwonek lub wibracje w zależności od ustawień urządzenia"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Może włączyć dzwonek lub wibracje w zależności od ustawień urządzenia. Rozmowy z aplikacji <xliff:g id="APP_NAME">%1$s</xliff:g> są domyślnie wyświetlane jako dymki."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Pozwól systemowi decydować, czy o powiadomieniu powinien informować dźwięk czy wibracja"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Stan:&lt;/b&gt; zmieniony na Domyślny"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Stan:&lt;/b&gt; zmieniono na Ciche"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Wybierz aplikację, do której chcesz dodać elementy sterujące"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Dodano # element sterujący.}few{Dodano # elementy sterujące.}many{Dodano # elementów sterujących.}other{Dodano # elementu sterującego.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Usunięto"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano do ulubionych"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano do ulubionych, pozycja <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Usunięto z ulubionych"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> w aplikacji <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Cofnij"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Przysuń się bliżej, aby odtwarzać na urządzeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Zbliż do urządzenia <xliff:g id="DEVICENAME">%1$s</xliff:g>, aby na nim odtwarzać"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Aby zagrać tutaj, przysuń bliżej urządzenie <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Odtwarzam na ekranie <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Coś poszło nie tak. Spróbuj ponownie."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Wczytuję"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Nieaktywny, sprawdź aplikację"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nie znaleziono"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Element jest niedostępny"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Błąd, spróbuj ponownie"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodaj elementy sterujące"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Edytuj elementy sterujące"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodaj urządzenia wyjściowe"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Wybrano 1 urządzenie"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Głośność"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Głośniki i wyświetlacze"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Proponowane urządzenia"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jak działa transmitowanie"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Transmisja"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osoby w pobliżu ze zgodnymi urządzeniami Bluetooth mogą słuchać transmitowanych przez Ciebie multimediów"</string>
@@ -1012,8 +1021,17 @@
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Rozłóż telefon, aby uzyskać lepszej jakości selfie"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Przełączyć na przedni wyświetlacz?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Użyj tylnego aparatu, aby zrobić szersze zdjęcie o większej rozdzielczości."</string>
-    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* Ekran się wyłączy"</b></string>
+    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"* Ten ekran się wyłączy"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Składane urządzenie jest rozkładane"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Składane urządzenie jest obracane"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Pozostało <xliff:g id="PERCENTAGE">%s</xliff:g> baterii"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Podłącz rysik do ładowarki"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Słaba bateria w rysiku"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Kamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nie można nawiązać połączenia z tego profilu"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Zasady obowiązujące w firmie zezwalają na nawiązywanie połączeń telefonicznych tylko w profilu służbowym"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Przełącz na profil służbowy"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zamknij"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 4192548..f1adbfd 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Borda inferior em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Borda esquerda em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Borda direita em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Capturas de tela do trabalho são salvas no app <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Gravador de tela"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processando gravação de tela"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação contínua para uma sessão de gravação de tela"</string>
@@ -450,7 +452,7 @@
     <string name="screen_pinning_title" msgid="9058007390337841305">"O app está fixado"</string>
     <string name="screen_pinning_description" msgid="8699395373875667743">"Ela é mantida à vista até que seja liberada. Toque em Voltar e em Visão geral e mantenha essas opções pressionadas para liberar."</string>
     <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Ela é mantida à vista até que seja liberada. Toque em Voltar e em Início e mantenha essas opções pressionadas para liberar."</string>
-    <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ele é mantido à vista até que seja liberado. Deslize para cima e mantenha pressionado para liberar."</string>
+    <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ele é mantido à vista até que seja liberado. Deslize para cima pressione para liberar."</string>
     <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ela é mantida à vista até que seja liberada. Toque em Visão geral e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ela é mantida à vista até que seja liberada. Toque em Início e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Dados pessoais podem ficar acessíveis (como contatos e conteúdo de e-mail)."</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Som e vibração desativados"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"O som e a vibração estão desativados, e o balão aparece na parte inferior da seção de conversa"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pode vibrar ou tocar com base nas configurações do smartphone"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pode vibrar ou tocar com base nas configurações do smartphone. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Pode vibrar ou tocar com base nas configurações do dispositivo"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Pode vibrar ou tocar com base nas configurações do dispositivo. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faça com que o sistema determine se a notificação resultará em som ou vibração"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; promovida a Padrão"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; rebaixada a Silenciosa"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# controle adicionado.}one{# controle adicionado.}many{# de controles adicionados.}other{# controles adicionados.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Removido"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Adicionar o app <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Quando você adiciona o app <xliff:g id="APPNAME">%s</xliff:g>, ele pode incluir controles e conteúdo neste painel. Em alguns casos, é possível escolher quais controles aparecem aqui."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Adicionado como favorito"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Adicionado como favorito (posição <xliff:g id="NUMBER">%d</xliff:g>)"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Removido dos favoritos"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime os dispositivos para tocar a mídia neste: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g> para abrir a mídia aqui"</string>
-    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Mídia aberta no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para abrir aqui, aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Tocando no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Algo deu errado. Tente novamente."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Carregando"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"O controle está indisponível"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Erro. Tente novamente"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controles"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Adicionar app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicionar saídas"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Alto-falantes e telas"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funciona a transmissão"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Transmitir"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As pessoas próximas a você com dispositivos Bluetooth compatíveis podem ouvir a mídia que você está transmitindo"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta tela vai ser desativada"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateria restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecte sua stylus a um carregador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da stylus fraca"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Filmadora"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Não é possível fazer uma ligação por este perfil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Sua política de trabalho só permite fazer ligações pelo perfil de trabalho"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Alternar para o perfil de trabalho"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fechar"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index ad93343..4aa89ea 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inferior de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite esquerdo de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite direito de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"As capturas de ecrã do trabalho são guardadas na app <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ficheiros"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Gravador de ecrã"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"A processar a gravação de ecrã"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação persistente de uma sessão de gravação de ecrã"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sem som ou vibração"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Sem som ou vibração e aparece na parte inferior na secção de conversas."</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pode tocar ou vibrar com base nas definições do telemóvel."</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pode tocar ou vibrar com base nas definições do telemóvel. As conversas da app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem como um balão por predefinição."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Pode tocar ou vibrar com base nas definições do dispositivo"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Pode tocar ou vibrar com base nas definições do dispositivo. As conversas da app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem como um balão por predefinição."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faça com que o sistema determine se esta notificação deve emitir um som ou uma vibração"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Estado:&lt;/b&gt; promovida para Predefinida"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Estado:&lt;/b&gt; despromovida para Silenciosa"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Escolha uma app para adicionar controlos"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# controlo adicionado.}many{# controlos adicionados.}other{# controlos adicionados.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Removido"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Adicionado aos favoritos"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Adicionados aos favoritos, posição <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Removido dos favoritos"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Anular"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime-se para reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g> para reproduzir aqui"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproduzir aqui, aproxime-se do <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"A reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Algo correu mal. Tente novamente."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"A carregar"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inativa. Consulte a app."</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado."</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"O controlo está indisponível"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Erro. Tente novamente."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controlos"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controlos"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicione saídas"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altifalantes e ecrãs"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funciona a transmissão"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Transmissão"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As pessoas próximas de si com dispositivos Bluetooth compatíveis podem ouvir o conteúdo multimédia que está a transmitir"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Este ecrã vai ser desligado"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável a ser desdobrado"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável a ser virado ao contrário"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de bateria restante"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ligue a caneta stylus a um carregador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da caneta stylus fraca"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Câmara de vídeo"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Não é possível ligar a partir deste perfil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"A sua Política de Trabalho só lhe permite fazer chamadas telefónicas a partir do perfil de trabalho"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Mudar para perfil de trabalho"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fechar"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 4192548..f1adbfd 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Borda inferior em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Borda esquerda em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Borda direita em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Capturas de tela do trabalho são salvas no app <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Gravador de tela"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processando gravação de tela"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação contínua para uma sessão de gravação de tela"</string>
@@ -450,7 +452,7 @@
     <string name="screen_pinning_title" msgid="9058007390337841305">"O app está fixado"</string>
     <string name="screen_pinning_description" msgid="8699395373875667743">"Ela é mantida à vista até que seja liberada. Toque em Voltar e em Visão geral e mantenha essas opções pressionadas para liberar."</string>
     <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"Ela é mantida à vista até que seja liberada. Toque em Voltar e em Início e mantenha essas opções pressionadas para liberar."</string>
-    <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ele é mantido à vista até que seja liberado. Deslize para cima e mantenha pressionado para liberar."</string>
+    <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"Ele é mantido à vista até que seja liberado. Deslize para cima pressione para liberar."</string>
     <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ela é mantida à vista até que seja liberada. Toque em Visão geral e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ela é mantida à vista até que seja liberada. Toque em Início e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Dados pessoais podem ficar acessíveis (como contatos e conteúdo de e-mail)."</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automática"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Som e vibração desativados"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"O som e a vibração estão desativados, e o balão aparece na parte inferior da seção de conversa"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Pode vibrar ou tocar com base nas configurações do smartphone"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Pode vibrar ou tocar com base nas configurações do smartphone. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Pode vibrar ou tocar com base nas configurações do dispositivo"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Pode vibrar ou tocar com base nas configurações do dispositivo. As conversas do app <xliff:g id="APP_NAME">%1$s</xliff:g> aparecem em balões por padrão."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Faça com que o sistema determine se a notificação resultará em som ou vibração"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; promovida a Padrão"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; rebaixada a Silenciosa"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# controle adicionado.}one{# controle adicionado.}many{# de controles adicionados.}other{# controles adicionados.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Removido"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Adicionar o app <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Quando você adiciona o app <xliff:g id="APPNAME">%s</xliff:g>, ele pode incluir controles e conteúdo neste painel. Em alguns casos, é possível escolher quais controles aparecem aqui."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Adicionado como favorito"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Adicionado como favorito (posição <xliff:g id="NUMBER">%d</xliff:g>)"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Removido dos favoritos"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime os dispositivos para tocar a mídia neste: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g> para abrir a mídia aqui"</string>
-    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Mídia aberta no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para abrir aqui, aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Tocando no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Algo deu errado. Tente novamente."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Carregando"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"O controle está indisponível"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Erro. Tente novamente"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controles"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Adicionar app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicionar saídas"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Alto-falantes e telas"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funciona a transmissão"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Transmitir"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As pessoas próximas a você com dispositivos Bluetooth compatíveis podem ouvir a mídia que você está transmitindo"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Esta tela vai ser desativada"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateria restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecte sua stylus a um carregador"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da stylus fraca"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Filmadora"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Não é possível fazer uma ligação por este perfil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Sua política de trabalho só permite fazer ligações pelo perfil de trabalho"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Alternar para o perfil de trabalho"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Fechar"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index e4a1b0b..894e22e 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Marginea de jos la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marginea stângă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marginea dreaptă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Capturile de ecran pentru serviciu sunt salvate în aplicația <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fișiere"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Recorder pentru ecran"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Se procesează înregistrarea"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificare în curs pentru o sesiune de înregistrare a ecranului"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automat"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Fără sunet sau vibrații"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Fără sunet sau vibrații și apare în partea de jos a secțiunii de conversație"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Poate să sune sau să vibreze, în funcție de setările telefonului"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Poate să sune sau să vibreze, în funcție de setările telefonului. Conversațiile din balonul <xliff:g id="APP_NAME">%1$s</xliff:g> în mod prestabilit."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Poate să sune sau să vibreze, în funcție de setările dispozitivului"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Poate să sune sau să vibreze, în funcție de setările dispozitivului. Conversațiile din balonul <xliff:g id="APP_NAME">%1$s</xliff:g> în mod prestabilit."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Solicită-i sistemului să stabilească dacă această notificare e sonoră sau cu vibrații."</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Stare:&lt;/b&gt; promovată la prestabilită"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Stare:&lt;/b&gt; setată ca Silențioasă"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Alege aplicația pentru a adăuga comenzi"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{S-a adăugat # comandă.}few{S-au adăugat # comenzi.}other{S-au adăugat # de comenzi.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Eliminată"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Adaugi <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Când adaugi <xliff:g id="APPNAME">%s</xliff:g>, aplicația poate să adauge comenzi și conținut pe acest panou. În anumite aplicații, poți să alegi comenzile care se afișează aici."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Marcată ca preferată"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Marcată ca preferată, poziția <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"S-a anulat marcarea ca preferată"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redă <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Anulează"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Apropie-te pentru a reda pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Apropie-te de <xliff:g id="DEVICENAME">%1$s</xliff:g> ca să redai acolo"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pentru a reda conținutul aici, apropie-te de <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Se redă pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"A apărut o eroare. Încearcă din nou."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Se încarcă"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tabletă"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verifică aplicația"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nu s-a găsit"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Comanda este indisponibilă"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Eroare, încearcă din nou"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Adaugă comenzi"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Editează comenzile"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Adaugă o aplicație"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adaugă ieșiri"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"S-a selectat un dispozitiv"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volum"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Difuzoare și afișaje"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispozitive sugerate"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cum funcționează transmisia"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Transmite"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Persoanele din apropiere cu dispozitive Bluetooth compatibile pot asculta conținutul pe care îl transmiți"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Acest ecran se va dezactiva"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispozitiv pliabil care este desfăcut"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispozitiv pliabil care este întors"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> baterie rămasă"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conectează-ți creionul la un încărcător"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Nivelul bateriei creionului este scăzut"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Cameră video"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nu poți iniția apeluri din acest profil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Politica privind activitatea îți permite să efectuezi apeluri telefonice numai din profilul de serviciu"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Comută la profilul de serviciu"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Închide"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 9b4975e..e55572c 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Граница снизу: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Граница слева: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Граница справа: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Скриншоты сохраняются в приложении \"<xliff:g id="APP">%1$s</xliff:g>\" в рабочем профиле"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Запись видео с экрана"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обработка записи с экрана…"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущее уведомление для записи видео с экрана"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматически"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звука и вибрации"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звука или вибрации, появляется в нижней части списка разговоров"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Звонок или вибрация в зависимости от настроек телефона"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Звонок или вибрация в зависимости от настроек телефона. Разговоры из приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" по умолчанию появляются в виде всплывающего чата"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Звонок или вибрация в зависимости от настроек устройства"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Звонок или вибрация в зависимости от настроек устройства. Разговоры из приложения \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" по умолчанию появляются в виде всплывающего чата."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Система будет сама определять, включать ли звуковой сигнал или вибрацию для уведомления"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Статус:&lt;/b&gt; повышено до уровня \"По умолчанию\""</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Статус:&lt;/b&gt; понижено до уровня \"Без звука\""</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Чтобы добавить виджеты управления, выберите приложение"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Добавлен # элемент управления.}one{Добавлен # элемент управления.}few{Добавлено # элемента управления.}many{Добавлено # элементов управления.}other{Добавлено # элемента управления.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Удалено"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Добавить приложение \"<xliff:g id="APPNAME">%s</xliff:g>\"?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Приложение \"<xliff:g id="APPNAME">%s</xliff:g>\" может добавить на эту панель элементы управления и контент. Некоторые приложения позволяют выбирать, какие элементы будут здесь показаны."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Добавлено в избранное"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Добавлено в избранное на позицию <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Не добавлено в избранное"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" из приложения \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Отменить"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Чтобы начать трансляцию на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", подойдите к нему ближе."</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Для воспроизведения на этом устройстве подойдите ближе к другому (<xliff:g id="DEVICENAME">%1$s</xliff:g>)."</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Чтобы начать воспроизведение здесь, подойдите ближе к устройству (<xliff:g id="DEVICENAME">%1$s</xliff:g>)."</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Воспроизводится на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\"."</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Произошла ошибка. Повторите попытку."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Загрузка…"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Нет ответа. Проверьте приложение."</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Не найдено."</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Управление недоступно"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Ошибка. Повторите попытку."</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Добавить виджеты"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Изменить виджеты"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Добавить приложение"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Добавление устройств вывода"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Группа"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрано 1 устройство"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Громкость"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Колонки и дисплеи"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Рекомендуемые устройства"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Как работают трансляции"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Трансляция"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Находящиеся рядом с вами люди с совместимыми устройствами Bluetooth могут слушать медиафайлы, которые вы транслируете."</string>
@@ -1010,10 +1016,19 @@
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Отмена"</string>
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Перевернуть сейчас"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Разложите телефон, чтобы селфи получилось лучше"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Перевернули телефон передним экраном к себе?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Перейти на передний экран?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Используйте основную камеру с широкоугольным объективом и высоким разрешением."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Этот экран отключится"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складное устройство в разложенном виде"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перевернутое складное устройство"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Уровень заряда батареи: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Поставьте стилус на зарядку."</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Низкий заряд батареи стилуса"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Видеокамера"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Невозможно совершить звонок из этого профиля"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Согласно правилам вашей организации вы можете совершать телефонные звонки только из рабочего профиля."</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Перейти в рабочий профиль"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Закрыть"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 91ed10f..8ea1695 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"පහළ සීමාව සියයට <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"වම් සීමාව සියයට <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"දකුණු සීමාව සියයට <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"වැඩ තිර රූ <xliff:g id="APP">%1$s</xliff:g> යෙදුම තුළ සුරැකෙයි"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ගොනු"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"තිර රෙකෝඩරය"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"තිර පටිගත කිරීම සකසමින්"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"තිර පටිගත කිරීමේ සැසියක් සඳහා කෙරෙන දැනුම් දීම"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ස්වයංක්‍රිය"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"හඬක් හෝ කම්පනයක් නැත"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"හඬක් හෝ කම්පනයක් නැති අතර සංවාද කොටසේ පහළම දිස් වේ"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"දුරකථන සැකසීම් මත පදනම්ව නාද කිරීමට හෝ කම්පනය කිරීමට හැකිය"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"දුරකථන සැකසීම් මත පදනම්ව නාද කිරීමට හෝ කම්පනය කිරීමට හැකිය. <xliff:g id="APP_NAME">%1$s</xliff:g> වෙතින් සංවාද පෙරනිමියෙන් බුබුළු දමයි"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"උපාංග සැකසීම් මත පදනම්ව නාද වීමට හෝ කම්පනය විය හැක"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"උපාංග සැකසීම් මත පදනම්ව නාද වීමට හෝ කම්පනය විය හැක. <xliff:g id="APP_NAME">%1$s</xliff:g> වෙතින් සංවාද පෙරනිමියෙන් බුබුළු දමයි."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"මෙම දැනුම් දීම ශබ්දයක් හෝ කම්පනයක් ඇති කළ යුතු ද යන්න පද්ධතිය මගින් තීරණය කර තිබේද"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;තත්ත්වය:&lt;/b&gt; පෙරනිමි වෙත ප්‍රවර්ධනය කරන ලදි"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;තත්ත්වය:&lt;/b&gt; නිශ්ශබ්ද වෙත පහත දමන ලදි"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"පාලන එක් කිරීමට යෙදුම තෝරා ගන්න"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# පාලනයක් එක් කර ඇත.}one{පාලන #ක් එක් කර ඇත.}other{පාලන #ක් එක් කර ඇත.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"ඉවත් කළා"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"ප්‍රියතම කළා"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ප්‍රියතම කළා, තත්ත්ව <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ප්‍රියතම වෙතින් ඉවත් කළා"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> වෙතින් වාදනය කරන්න"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"පසුගමනය කරන්න"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කිරීමට වඩාත් ළං වන්න"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"මෙහි ක්‍රීඩා කිරීමට <xliff:g id="DEVICENAME">%1$s</xliff:g> වෙත වඩා සමීප වන්න"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"මෙහි වාදනය කිරීම සඳහා, <xliff:g id="DEVICENAME">%1$s</xliff:g> වෙත සමීප වන්න"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කරමින්"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"යම් දෙයක් වැරදිණි. නැවත උත්සාහ කරන්න."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"පූරණය වේ"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ටැබ්ලටය"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"අක්‍රියයි, යෙදුම පරීක්ෂා කරන්න"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"හමු නොවිණි"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"පාලනය ලබා ගත නොහැකිය"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"දෝෂයකි, නැවත උත්සාහ කරන්න"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"පාලන එක් කරන්න"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"පාලන සංස්කරණය කරන්න"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"ප්‍රතිදාන එක් කරන්න"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"සමූහය"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"උපාංග 1ක් තෝරන ලදී"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"හඬ පරිමාව"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ස්පීකර් සහ සංදර්ශක"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"යෝජිත උපාංග"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"විකාශනය ක්‍රියා කරන ආකාරය"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"විකාශනය"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ගැළපෙන බ්ලූටූත් උපාංග සහිත ඔබ අවට සිටින පුද්ගලයින්ට ඔබ විකාශනය කරන මාධ්‍යයට සවන් දිය හැකිය"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ මෙම තිරය ක්‍රියා විරහිත වනු ඇත"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"දිග හැරෙමින් පවතින නැමිය හැකි උපාංගය"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"වටා පෙරළෙමින් තිබෙන නැමිය හැකි උපාංගය"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> බැටරිය ඉතිරිව ඇත"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ඔබේ පන්හිඳ චාජරයකට සම්බන්ධ කරන්න"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"පන්හිඳ බැටරිය අඩුයි"</string>
+    <string name="video_camera" msgid="7654002575156149298">"වීඩියෝ කැමරාව"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"මෙම පැතිකඩෙන් ඇමතීමට නොහැක"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"ඔබේ වැඩ ප්‍රතිපත්තිය ඔබට කාර්යාල පැතිකඩෙන් පමණක් දුරකථන ඇමතුම් ලබා ගැනීමට ඉඩ සලසයි"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"කාර්යාල පැතිකඩ වෙත මාරු වන්න"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"වසන්න"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 4c3d8b8..e321fcb 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> %% dolnej hranice"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> %% ľavej hranice"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> %% pravej hranice"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Pracovné snímky obrazovky sú uložené v aplikácii <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Rekordér obrazovky"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Spracúva sa záznam obrazovky"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Zobrazuje sa upozornenie týkajúce sa relácie záznamu obrazovky"</string>
@@ -375,9 +377,9 @@
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Celá obrazovka"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Jedna aplikácia"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Počas zdieľania, nahrávania alebo prenosu bude mať <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> prístup k všetkému na obrazovke, prípadne k obsahu, ktorý sa bude v zariadení prehrávať. Preto venujte zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Počas zdieľania, nahrávania alebo prenosu bude mať aplikácia <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> prístup k všetkému obsahu, ktorý sa v nej bude zobrazovať alebo prehrávať. Preto venujte zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Keď zdieľate, nahrávate alebo prenášate nejakú aplikáciu, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má prístup k všetkému obsahu, ktorý sa v aplikácii zobrazuje alebo prehráva. Dajte preto pozor na heslá, platobné údaje, osobné správy a iné citlivé údaje."</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovať"</string>
-    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Aplikácia na zdieľanie alebo nahrávanie"</string>
+    <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Vyberte aplikáciu, ktorú chcete zdieľať alebo nahrávať"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Chcete povoliť tejto aplikácii zdieľať alebo nahrávať?"</string>
     <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Počas zdieľania, nahrávania alebo prenosu bude mať táto aplikácia prístup k všetkému na obrazovke, prípadne k obsahu, ktorý sa bude v zariadení prehrávať. Venujte preto zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
     <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Počas zdieľania, nahrávania alebo prenosu bude mať táto aplikácia prístup k všetkému obsahu, ktorý sa v nej bude zobrazovať alebo prehrávať. Venujte preto zvýšenú pozornosť heslám, platobným údajom, správam či ďalším citlivým údajom."</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automaticky"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Žiadny zvuk ani vibrácie"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Žiadny zvuk ani vibrácie a zobrazuje sa nižšie v sekcii konverzácií"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Môže zvoniť či vibrovať podľa nastavení telefónu"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Môže zvoniť alebo vibrovať podľa nastavení telefónu. Predvolene sa zobrazia konverzácie z bubliny <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Môže zvoniť či vibrovať podľa nastavení v zariadení"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Môže zvoniť alebo vibrovať podľa nastavení v zariadení. Predvolene sa zobrazia konverzácie z bubliny aplikácie <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Nechajte systém určiť, či má toto upozornenie vydávať zvuk alebo vibrovať"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Stav:&lt;/b&gt; Preradené vyššie do kategórie Predvolené"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Preradené nižšie do kategórie Tiché"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikáciu, ktorej ovládače si chcete pridať"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Bol pridaný # ovládací prvok.}few{Boli pridané # ovládacie prvky.}many{# controls added.}other{Bolo pridaných # ovládacích prvkov.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Odstránené"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Chcete pridať aplikáciu <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Keď pridáte aplikáciu <xliff:g id="APPNAME">%s</xliff:g>, bude môcť pridať ovládanie a obsah na tento panel. V prípade niektorých aplikácií môžete vybrať, ktoré ovládacie prvky sa tu majú zobraziť."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Pridané medzi obľúbené"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Pridané medzi obľúbené, pozícia <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Odstránené z obľúbených"</string>
@@ -849,12 +853,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Späť"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ak chcete prehrávať v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>, priblížte sa"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Ak chcete prehrávať v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>, priblížte sa k nemu"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ak chcete prehrávať v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>, priblížte sa k nemu"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ak tu chcete prehrávať obsah, priblížte zariadenie k zariadeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Prehráva sa v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Niečo sa pokazilo. Skúste to znova."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Načítava sa"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktívne, preverte aplikáciu"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nenájdené"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládač nie je k dispozícii"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Chyba, skúste to znova"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Pridať ovládače"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Upraviť ovládače"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Pridať aplikáciu"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Pridanie výstupov"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 vybrané zariadenie"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Hlasitosť"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Reproduktory a obrazovky"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Navrhované zariadenia"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Ako vysielanie funguje"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Vysielanie"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ľudia v okolí s kompatibilnými zariadeniami s rozhraním Bluetooth si môžu vypočuť médiá, ktoré vysielate"</string>
@@ -1008,12 +1014,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• K dispozícii je minimálne jedno zariadenie"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Pridržte skratku"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Zrušiť"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Prevráťte"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Otočiť"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Ak chcete lepšie selfie, rozložte telefón"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Prevrátiť na pred. obrazovku pre lepšie selfie?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Otočiť na prednú obrazovku pre lepšie selfie?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Pomocou zadného fotoaparátu vytvorte širšiu fotku s vyšším rozlíšením."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Táto obrazovka sa vypne"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozloženie skladacieho zariadenia"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Prevrátenie skladacieho zariadenia"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Zostáva <xliff:g id="PERCENTAGE">%s</xliff:g> batérie"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Pripojte dotykové pero k nabíjačke"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stav batérie dotykového pera je nízky"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Z tohto profilu nemôžete volať"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Pracovné pravidlá vám umožňujú telefonovať iba v pracovnom profile"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Prepnúť na pracovný profil"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zavrieť"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index df5dcb9..a5989bd 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Meja spodaj <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Meja levo <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Meja desno <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Posnetki zaslona v delovnem profilu so shranjeni v aplikaciji <xliff:g id="APP">%1$s</xliff:g>."</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Datoteke"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Snemalnik zaslona"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obdelava videoposnetka zaslona"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Nenehno obveščanje o seji snemanja zaslona"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Samodejno"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Brez zvočnega opozarjanja ali vibriranja."</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Brez zvočnega opozarjanja ali vibriranja, prikaz nižje v razdelku Pogovor."</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Zvonjenje ali vibriranje je omogočeno na podlagi nastavitev telefona."</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Zvonjenje ali vibriranje je omogočeno na podlagi nastavitev telefona. Pogovori v aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> so privzeto prikazani v oblačkih."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Zvonjenje ali vibriranje je omogočeno na podlagi nastavitev naprave."</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Zvonjenje ali vibriranje je omogočeno na podlagi nastavitev naprave. Pogovori v aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> so privzeto prikazani v oblačkih."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Naj sistem določi, ali ob prejemu tega obvestila naprava predvaja zvok ali zavibrira"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Stanje:&lt;/b&gt; Uvrščeno med privzeta obvestila"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Stanje:&lt;/b&gt; Uvrščeno med obvestila brez zvoka"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Izberite aplikacijo za dodajanje kontrolnikov"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrolnik je dodan.}one{# kontrolnik je dodan.}two{# kontrolnika sta dodana.}few{# kontrolniki so dodani.}other{# kontrolnikov je dodanih.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Odstranjeno"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano med priljubljene"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano med priljubljene, položaj <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Odstranjeno iz priljubljenih"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Razveljavi"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Za predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g> bolj približajte telefon."</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Približajte napravi <xliff:g id="DEVICENAME">%1$s</xliff:g> za predvajanje v tej napravi"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Za predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g> bolj približajte telefon"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Če želite predvajati tukaj, se približajte napravi <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Prišlo je do napake. Poskusite znova."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Nalaganje"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablični računalnik"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, poglejte aplikacijo"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Ni mogoče najti"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolnik ni na voljo"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Napaka, poskusite znova"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Dodajte kontrolnike"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Uredite kontrolnike"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajanje izhodov"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izbrana je ena naprava"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Glasnost"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvočniki in zasloni"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predlagane naprave"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako deluje oddajanje"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Oddajanje"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osebe v bližini z združljivo napravo Bluetooth lahko poslušajo predstavnost, ki jo oddajate."</string>
@@ -1008,12 +1017,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Na voljo mora biti vsaj ena naprava."</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Pridržite bližnjico"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Prekliči"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Obrnite"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Obrni"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Razprite telefon za boljši selfi"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Obrnite telefon na sprednji zaslon za boljši selfi"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Uporabite hrbtni fotoaparat, da posnamete širšo sliko višje ločljivosti."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ta zaslon se bo izklopil."</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Razpiranje zložljive naprave"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Obračanje zložljive naprave"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostanek energije baterije: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Povežite pisalo s polnilnikom."</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Skoraj prazna baterija pisala"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ni mogoče klicati iz tega profila"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Službeni pravilnik dovoljuje opravljanje telefonskih klicev le iz delovnega profila."</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Preklopi na delovni profil"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Zapri"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index ad2e596..a6db996 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Kufiri i poshtëm <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kufiri i majtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Kufiri i djathtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Pamjet e ekranit të punës janë ruajtur në aplikacionin \"<xliff:g id="APP">%1$s</xliff:g>\""</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Skedarë"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Regjistruesi i ekranit"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Regjistrimi i ekranit po përpunohet"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Njoftim i vazhdueshëm për një seancë regjistrimi të ekranit"</string>
@@ -372,7 +374,7 @@
     <string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Do të fillosh regjistrimin ose transmetimin?"</string>
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"Fillo regjistrimin ose transmetimin me <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
     <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Të lejohet <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> të shpërndajë ose regjistrojë?"</string>
-    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Ekran i plotë"</string>
+    <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Të gjithë ekranin"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"Vetëm një aplikacion"</string>
     <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"Gjatë shpërndarjes, regjistrimit ose transmetimit, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Gjatë shpërndarjes, regjistrimit ose transmetimit të një aplikacioni, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatike"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Asnjë tingull ose dridhje"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Asnjë tingull ose dridhje dhe shfaqet më poshtë në seksionin e bisedave"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Mund të bjerë zilja ose të dridhet në bazë të cilësimeve të telefonit"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Mund të bjerë zilja ose të dridhet në bazë të cilësimeve të telefonit. Si parazgjedhje, bisedat nga <xliff:g id="APP_NAME">%1$s</xliff:g> shfaqen si flluska."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Mund të bjerë zilja ose të dridhet në bazë të cilësimeve të pajisjes"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Mund të bjerë zilja ose të dridhet në bazë të cilësimeve të pajisjes. Bisedat nga flluska e <xliff:g id="APP_NAME">%1$s</xliff:g> si parazgjedhje."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Kërkoji sistemit të përcaktojë nëse ky njoftim duhet të lëshojë tingull apo dridhje"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Statusi:&lt;/b&gt; Promovuar si parazgjedhje"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Statusi:&lt;/b&gt; Ulur në nivel si në heshtje"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Zgjidh aplikacionin për të shtuar kontrollet"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{U shtua # kontroll.}other{U shtuan # kontrolle.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"E hequr"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Të shtohet <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Kur shton <xliff:g id="APPNAME">%s</xliff:g>, ai mund t\'i shtojë kontrolle dhe përmbajtje këtij paneli. Në disa aplikacione, mund të zgjedhësh se cilat kontrolle shfaqen këtu."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"E shtuar te të preferuarat"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"E shtuar te të preferuarat, pozicioni <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"E hequr nga të preferuarat"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Zhbëj"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Afrohu për të luajtur në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Afrohu te <xliff:g id="DEVICENAME">%1$s</xliff:g> për ta luajtur këtu"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Për ta luajtur këtu, afrohu më shumë te <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Po luhet në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Ndodhi një gabim. Provo përsëri."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Po ngarkohet"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Joaktive, kontrollo aplikacionin"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Nuk u gjet"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolli është i padisponueshëm"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Gabim, provo sërish"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Shto kontrollet"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Modifiko kontrollet"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Shto një aplikacion"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Shto daljet"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupi"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 pajisje e zgjedhur"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volumi"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altoparlantët dhe ekranet"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Pajisjet e sugjeruara"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Si funksionon transmetimi"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Transmetimi"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personat në afërsi me ty me pajisje të përputhshme me Bluetooth mund të dëgjojnë median që ti po transmeton"</string>
@@ -1008,12 +1014,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Ofrohet të paktën një pajisje"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Prek dhe mbaj shtypur shkurtoren"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Anulo"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"U kthye tani"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Ktheje tani"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Shpalos telefonin për një selfi më të mirë"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Të kthehet tek ekrani para për selfi më të mirë?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Përdor lenten e kamerës së pasme për një fotografi më të gjerë me rezolucion më të lartë."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ky ekran do të fiket"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Pajisja e palosshme duke u hapur"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Pajisja e palosshme duke u rrotulluar"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Përqindja e mbetur e baterisë: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Lidhe stilolapsin me një karikues"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bateria e stilolapsit në nivel të ulët"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Nuk mund të telefonosh nga ky profil"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Politika jote e punës të lejon të bësh telefonata vetëm nga profili i punës"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Kalo te profili i punës"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Mbyll"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index ef6bb45..a3ed992 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Доња ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лева ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Десна ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Снимци екрана за посао се чувају у апликацији <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Фајлови"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Снимач екрана"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обрађујемо видео снимка екрана"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Обавештење о сесији снимања екрана је активно"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Аутоматска"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звука и вибрирања"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звука и вибрирања и приказује се у наставку одељка за конверзације"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Може да звони или вибрира у зависности од подешавања телефона"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Може да звони или вибрира у зависности од подешавања телефона. Конверзације из апликације <xliff:g id="APP_NAME">%1$s</xliff:g> се подразумевано приказују у облачићима."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Може да звони или вибрира у зависности од подешавања уређаја"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Може да звони или вибрира у зависности од подешавања уређаја. Конверзације из апликације <xliff:g id="APP_NAME">%1$s</xliff:g> подразумевано се приказују у облачићима."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Нека систем утврди да ли ово обавештење треба да емитује звук или да вибрира"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Статус:&lt;/b&gt; Унапређено у Подразумевано"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Статус:&lt;/b&gt; Деградирано у Нечујно"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Одаберите апликацију за додавање контрола"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# контрола је додата.}one{# контрола је додата.}few{# контроле су додате.}other{# контрола је додато.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Уклоњено"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Желите ли да додате <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Када додате апликацију <xliff:g id="APPNAME">%s</xliff:g>, она може да додаје контроле и садржај у ово окно. У неким апликацијама можете да изаберете које ће се контроле овде приказивати."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Означено је као омиљено"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Означено је као омиљено, <xliff:g id="NUMBER">%d</xliff:g>. позиција"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Уклоњено је из омиљених"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Опозови"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Приближите да бисте пуштали музику на: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Приближите се уређају <xliff:g id="DEVICENAME">%1$s</xliff:g> да бисте на њему пуштали"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Да бисте пуштали садржај овде, приближите уређају <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Пушта се на уређају <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Дошло је до грешке. Пробајте поново."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Учитава се"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно. Видите апликацију"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Није пронађено"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Контрола није доступна"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Грешка. Пробајте поново"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Додај контроле"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Измени контроле"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Додај апликацију"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додајте излазе"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Изабран је 1 уређај"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Звук"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Звучници и екрани"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Предложени уређаји"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Како функционише емитовање"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Емитовање"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Људи у близини са компатибилним Bluetooth уређајима могу да слушају медијски садржај који емитујете"</string>
@@ -1008,12 +1014,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• да је доступан барем један уређај"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Додирните и задржите пречицу"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Откажи"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Обрните"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Обрни"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Отворите телефон за бољи селфи"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Желите да обрнете на предњи екран за бољи селфи?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Користите задњу камеру да бисте снимили ширу слику са вишом резолуцијом."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Овај екран ће се искључити"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Уређај на преклоп се отвара"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Уређај на преклоп се обрће"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Преостало је још<xliff:g id="PERCENTAGE">%s</xliff:g> батерије"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Повежите писаљку са пуњачем"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Низак ниво батерије писаљке"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Видео камера"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Не можете да упућујете позиве са овог профила"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Смернице за посао вам омогућавају да телефонирате само са пословног профила"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Пређи на пословни профил"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Затвори"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index c982739..41fe502 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Nedre gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vänster gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Höger gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Jobbskärmbilder sparas i <xliff:g id="APP">%1$s</xliff:g>-appen"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Skärminspelare"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Behandlar skärminspelning"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Avisering om att skärminspelning pågår"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Automatiskt"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Inga ljud eller vibrationer"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Inga ljud eller vibrationer och visas längre ned bland konversationerna"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Kan ringa eller vibrera beroende på inställningarna på telefonen"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Kan ringa eller vibrera beroende på inställningarna på telefonen. Konversationer från <xliff:g id="APP_NAME">%1$s</xliff:g> visas i bubblor som standard."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Kan ringa eller vibrera beroende på inställningarna på enheten"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Kan ringa eller vibrera beroende på inställningarna på enheten. Konversationer från <xliff:g id="APP_NAME">%1$s</xliff:g> visas i bubblor som standard."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Låt systemet avgöra om den här aviseringen ska låta eller vibrera"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Ändrad till Standard"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Ändrad till Tyst"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Välj en app om du vill lägga till snabbkontroller"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontroll har lagts till.}other{# kontroller har lagts till.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Har tagits bort"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Har lagts till som favorit"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Har lagts till som favorit, plats <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Har tagits bort från favoriter"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> från <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Ångra"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flytta närmare för att spela upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Flytta dig närmare <xliff:g id="DEVICENAME">%1$s</xliff:g> om du vill spela här"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Kom närmare <xliff:g id="DEVICENAME">%1$s</xliff:g> om du vill spela upp här"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Spelas upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Något gick fel. Försök igen."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Läser in"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"surfplatta"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv, kolla appen"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Hittades inte"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Styrning är inte tillgänglig"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Fel, försök igen"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Lägg till snabbkontroller"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Redigera snabbkontroller"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Lägg till utgångar"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet har valts"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volym"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Högtalare och skärmar"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Förslag på enheter"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Så fungerar utsändning"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Utsändning"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personer i närheten med kompatibla Bluetooth-enheter kan lyssna på medieinnehåll som du sänder ut"</string>
@@ -1010,10 +1019,19 @@
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Avbryt"</string>
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Vänd nu"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Vik upp telefonen för att ta en bättre selfie"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vill du ta en bättre selfie med främre skärmen?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Vill du ta en bättre selfie med främre kameran?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Använd den bakre kameran för att ta ett mer vidsträckt foto med högre upplösning."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Den här skärmen inaktiveras"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En vikbar enhet viks upp"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En vikbar enhet vänds"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> av batteriet återstår"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Anslut e-pennan till en laddare"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"E-pennans batterinivå är låg"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Det går inte att ringa från den här profilen"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Jobbprincipen tillåter endast att du ringer telefonsamtal från jobbprofilen"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Byt till jobbprofilen"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Stäng"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 5e33bf4..a126e7e 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Mpaka wa sehemu ya chini wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Mpaka wa sehemu ya kushoto wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Mpaka wa sehemu ya kulia wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Picha ya skrini ya kazi huhifadhiwa kwenye programu ya <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Kinasa Skrini"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Inachakata rekodi ya skrini"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Arifa inayoendelea ya kipindi cha kurekodi skrini"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Otomatiki"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Hakuna sauti wala mtetemo"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Hakuna sauti wala mtetemo na huonekana upande wa chini katika sehemu ya mazungumzo"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Huenda ikalia au kutetema kulingana na mipangilio ya simu"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Huenda ikalia au kutetema kulingana na mipangilio ya simu. Mazungumzo kutoka kiputo cha <xliff:g id="APP_NAME">%1$s</xliff:g> kwa chaguomsingi."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Huenda ikalia au kutetema kulingana na mipangilio ya kifaa"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Huenda ikalia au kutetema kulingana na mipangilio ya kifaa. Mazungumzo kutoka kiputo cha <xliff:g id="APP_NAME">%1$s</xliff:g> kwa chaguomsingi."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Ruhusu mfumo ubainishe iwapo arifa hii inapaswa kutoa sauti au mtetemo"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Hali:&lt;/b&gt; Imepandishwa Hadhi Kuwa Chaguomsingi"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Imeshushwa Hadhi Kuwa Kimya"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Chagua programu ili uweke vidhibiti"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Umeweka kidhibiti #.}other{Umeweka vidhibiti #.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Kimeondolewa"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Kimewekwa kwenye vipendwa"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Kimewekwa kwenye vipendwa, nafasi ya <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Kimeondolewa kwenye vipendwa"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> katika <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Tendua"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Sogea karibu ili ucheze kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Sogeza karibu na <xliff:g id="DEVICENAME">%1$s</xliff:g> ili kucheza hapa"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Sogeza karibu ili ucheze kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ili ucheze maudhui kwenye kifaa hiki, sogeza karibu na <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Inacheza kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Hitilafu fulani imetokea. Jaribu tena."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Inapakia"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"kompyuta kibao"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Haitumiki, angalia programu"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Hakipatikani"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kidhibiti hakipatikani"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Hitilafu, jaribu tena"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Weka vidhibiti"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Badilisha vidhibiti"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Weka vifaa vya kutoa sauti"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Kikundi"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Umechagua kifaa 1"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Sauti"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Spika na Skrini"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Vifaa Vilivyopendekezwa"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jinsi utangazaji unavyofanya kazi"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Tangaza"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Watu walio karibu nawe wenye vifaa oanifu vya Bluetooth wanaweza kusikiliza maudhui unayoyatangaza"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Skrini hii itajizima"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Kifaa kinachokunjwa kikikunjuliwa"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Kifaa kinachokunjwa kikigeuzwa"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Chaji ya betri imesalia <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Unganisha stylus yako kwenye chaja"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Chaji ya betri ya Stylus imepungua"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Kamera ya kuchukulia video"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Huwezi kupiga simu kutoka kwenye wasifu huu"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Sera ya mahali pako pa kazi inakuruhusu upige simu kutoka kwenye wasifu wa kazini pekee"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Tumia wasifu wa kazini"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Funga"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 6c7cab5..5d78e4e 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -28,6 +28,7 @@
 
     <!-- QS-->
     <dimen name="qs_panel_padding_top">16dp</dimen>
+    <dimen name="qs_panel_padding">24dp</dimen>
     <dimen name="qs_content_horizontal_padding">24dp</dimen>
     <dimen name="qs_horizontal_margin">24dp</dimen>
     <!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2,
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index f4434e8..ea3c012 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -36,4 +36,15 @@
     <integer name="qs_security_footer_maxLines">1</integer>
 
     <bool name="config_use_large_screen_shade_header">true</bool>
+
+    <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
+    string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
+    separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
+    default is displayed by System UI as long as the user hasn't made a different choice for that
+    slot. If the user did make a choice, even if the choice is the "None" option, the default is
+    ignored. -->
+    <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
+        <item>bottom_start:home</item>
+        <item>bottom_end:create_note</item>
+    </string-array>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 9bc0dde..4f24d83 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -30,10 +30,6 @@
     <!-- Margin on the left side of the carrier text on Keyguard -->
     <dimen name="keyguard_carrier_text_margin">24dp</dimen>
 
-    <!-- The width/height of the phone/camera/unlock icon on keyguard. -->
-    <dimen name="keyguard_affordance_height">80dp</dimen>
-    <dimen name="keyguard_affordance_width">120dp</dimen>
-
     <!-- Screen pinning request width -->
     <dimen name="screen_pinning_request_width">400dp</dimen>
     <!-- Screen pinning request bottom button circle widths -->
@@ -55,9 +51,6 @@
     <!-- Text size for user name in user switcher -->
     <dimen name="kg_user_switcher_text_size">18sp</dimen>
 
-    <dimen name="controls_header_bottom_margin">12dp</dimen>
-    <dimen name="controls_top_margin">24dp</dimen>
-
     <dimen name="global_actions_grid_item_layout_height">80dp</dimen>
 
     <dimen name="qs_brightness_margin_bottom">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 122806a..2b88e55 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -17,7 +17,6 @@
 */
 -->
 <resources>
-    <dimen name="controls_padding_horizontal">205dp</dimen>
     <dimen name="split_shade_notifications_scrim_margin_bottom">24dp</dimen>
     <dimen name="notification_panel_margin_bottom">64dp</dimen>
 
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 927059a..8f59df6 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -19,7 +19,7 @@
     <!-- gap on either side of status bar notification icons -->
     <dimen name="status_bar_icon_padding">1dp</dimen>
 
-    <dimen name="controls_padding_horizontal">75dp</dimen>
+    <dimen name="controls_padding_horizontal">40dp</dimen>
 
     <dimen name="large_screen_shade_header_height">56dp</dimen>
 
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 6e14c26..a5528a1 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"கீழ் எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"இடது எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"வலது எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"பணிக் கணக்கு ஸ்கிரீன்ஷாட்டுகள் <xliff:g id="APP">%1$s</xliff:g> ஆப்ஸில் சேமிக்கப்படும்"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"ஸ்கிரீன் ரெக்கார்டர்"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ஸ்க்ரீன் ரெக்கார்டிங் செயலாக்கப்படுகிறது"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"திரை ரெக்கார்டிங் அமர்விற்கான தொடர் அறிவிப்பு"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"தானியங்கு"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"ஒலி / அதிர்வு இல்லை"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ஒலி / அதிர்வு இல்லாமல் உரையாடல் பிரிவின் கீழ்ப் பகுதியில் தோன்றும்"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"மொபைல் அமைப்புகளின் அடிப்படையில் ஒலிக்கலாம்/அதிரலாம்"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"மொபைல் அமைப்புகளின் அடிப்படையில் ஒலிக்கவோ அதிரவோ செய்யும். <xliff:g id="APP_NAME">%1$s</xliff:g> இலிருந்து வரும் உரையாடல்கள் இயல்பாகவே குமிழாகத் தோன்றும்."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"சாதன அமைப்புகளைப் பொறுத்து ஒலிக்கக்கூடும் அல்லது அதிர்வடையக்கூடும்"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"சாதன அமைப்புகளைப் பொறுத்து ஒலிக்கக்கூடும் அல்லது அதிர்வடையக்கூடும். இயல்பாக, <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸில் பெறப்படும் உரையாடல் அறிவிப்புகள் குமிழ்களாகத் தோன்றும்."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"இந்த அறிவிப்பு ஒலி எழுப்ப வேண்டுமா அதிர வேண்டுமா என்பதை சிஸ்டம் தீர்மானிக்கும்"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;நிலை:&lt;/b&gt; இயல்புநிலைக்கு உயர்த்தி அமைக்கப்பட்டது"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;நிலை:&lt;/b&gt; சைலன்ட் நிலைக்குக் குறைத்து அமைக்கப்பட்டது"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"கட்டுப்பாடுகளைச் சேர்க்க வேண்டிய ஆப்ஸைத் தேர்ந்தெடுங்கள்"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# கட்டுப்பாடு சேர்க்கப்பட்டது.}other{# கட்டுப்பாடுகள் சேர்க்கப்பட்டன.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"அகற்றப்பட்டது"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"பிடித்தவற்றில் சேர்க்கப்பட்டது"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"பிடித்தவற்றில் சேர்க்கப்பட்டது, நிலை <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"பிடித்தவற்றிலிருந்து நீக்கப்பட்டது"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%2$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"செயல்தவிர்"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் இயக்க உங்கள் சாதனத்தை அருகில் எடுத்துச் செல்லுங்கள்"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"இங்கு பிளே செய்ய உங்கள் சாதனத்தை <xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்திற்கு அருகில் நகர்த்துங்கள்"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"இங்கு பிளே செய்ய உங்கள் சாதனத்தை <xliff:g id="DEVICENAME">%1$s</xliff:g>அருகில் நகர்த்துங்கள்"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் பிளே ஆகிறது"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"ஏதோ தவறாகிவிட்டது. மீண்டும் முயலவும்."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"ஏற்றுகிறது"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"டேப்லெட்"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"செயலில் இல்லை , சரிபார்க்கவும்"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"இல்லை"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"கட்டுப்பாடு இல்லை"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"பிழை, மீண்டும் முயலவும்"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"கட்டுப்பாடுகளைச் சேர்த்தல்"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"கட்டுப்பாடுகளை மாற்றுதல்"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"அவுட்புட்களைச் சேர்த்தல்"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"குழு"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 சாதனம் தேர்ந்தெடுக்கப்பட்டுள்ளது"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ஒலியளவு"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ஸ்பீக்கர்கள் &amp; டிஸ்ப்ளேக்கள்"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"பரிந்துரைக்கப்படும் சாதனங்கள்"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"பிராட்காஸ்ட் எவ்வாறு செயல்படுகிறது?"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"பிராட்காஸ்ட்"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"நீங்கள் பிராட்காஸ்ட் செய்யும் மீடியாவை அருகிலுள்ளவர்கள் இணக்கமான புளூடூத் சாதனங்கள் மூலம் கேட்கலாம்"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ இந்தத் திரை ஆஃப் ஆகிவிடும்"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"மடக்கத்தக்க சாதனம் திறக்கப்படுகிறது"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"மடக்கத்தக்க சாதனம் ஃபிளிப் செய்யப்பட்டு திருப்பப்படுகிறது"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> பேட்டரி மீதமுள்ளது"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"உங்கள் ஸ்டைலஸைச் சார்ஜருடன் இணையுங்கள்"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"ஸ்டைலஸின் பேட்டரி குறைவாக உள்ளது"</string>
+    <string name="video_camera" msgid="7654002575156149298">"வீடியோ கேமரா"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"இந்தக் கணக்கிலிருந்து அழைக்க முடியாது"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"உங்கள் பணிக் கொள்கையின்படி நீங்கள் பணிக் கணக்கில் இருந்து மட்டுமே ஃபோன் அழைப்புகளைச் செய்ய முடியும்"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"பணிக் கணக்கிற்கு மாறு"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"மூடுக"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index d4e458f..1b82e39 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"దిగువ సరిహద్దు <xliff:g id="PERCENT">%1$d</xliff:g> శాతం"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ఎడమ వైపు సరిహద్దు <xliff:g id="PERCENT">%1$d</xliff:g> శాతం"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"కుడి వైపు సరిహద్దు <xliff:g id="PERCENT">%1$d</xliff:g> శాతం"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"వర్క్ స్క్రీన్‌షాట్‌లు <xliff:g id="APP">%1$s</xliff:g> యాప్‌లో సేవ్ చేయబడతాయి"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ఫైల్స్"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"స్క్రీన్ రికార్డర్"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"స్క్రీన్ రికార్డింగ్ అవుతోంది"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"స్క్రీన్ రికార్డ్ సెషన్ కోసం ఆన్‌గోయింగ్ నోటిఫికేషన్"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"ఆటోమేటిక్"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"శబ్దం లేదా వైబ్రేషన్‌లు ఏవీ లేవు"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"శబ్దం లేదా వైబ్రేషన్ లేదు, సంభాషణ విభాగం దిగువన కనిపిస్తుంది"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"ఫోన్ సెట్టింగ్‌ల ఆధారంగా రింగ్ లేదా వైబ్రేట్ కావచ్చు"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"ఫోన్ సెట్టింగ్‌ల ఆధారంగా రింగ్ లేదా వైబ్రేట్ కావచ్చు. <xliff:g id="APP_NAME">%1$s</xliff:g> నుండి సంభాషణలు ఆటోమేటిక్‌గా బబుల్‌గా కనిపిస్తాయి."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"పరికర సెట్టింగ్‌ల ఆధారంగా రింగ్ లేదా వైబ్రేట్ కావచ్చు"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"పరికర సెట్టింగ్‌ల ఆధారంగా రింగ్ లేదా వైబ్రేట్ కావచ్చు. <xliff:g id="APP_NAME">%1$s</xliff:g> నుండి సంభాషణలు ఆటోమేటిక్‌గా బబుల్‌లో కనిపిస్తాయి."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ఈ నోటిఫికేషన్ వచ్చినప్పుడు శబ్దం చేయాలా లేదా వైబ్రేట్ చేయాలా అనేది నిర్ణయించడానికి సిస్టమ్‌కు అనుమతి ఇవ్వండి"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;స్టేటస్:&lt;/b&gt; ఆటోమేటిక్ సెట్టింగ్‌కు ప్రోమోట్ చేయబడింది"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;స్టేటస్:&lt;/b&gt; నిశ్శబ్దం స్థాయికి తగ్గించబడింది"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"కంట్రోల్స్‌ను యాడ్ చేయడానికి యాప్‌ను ఎంచుకోండి"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# కంట్రోల్ జోడించబడింది.}other{# కంట్రోల్స్ జోడించబడ్డాయి.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"తీసివేయబడింది"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"ఇష్టమైనదిగా గుర్తు పెట్టబడింది"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"<xliff:g id="NUMBER">%d</xliff:g>వ స్థానంలో ఇష్టమైనదిగా గుర్తు పెట్టబడింది"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ఇష్టమైనదిగా పెట్టిన గుర్తు తీసివేయబడింది"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> నుండి <xliff:g id="SONG_NAME">%1$s</xliff:g>‌ను ప్లే చేయండి"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"చర్య రద్దు"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>‌లో ప్లే చేయడానికి దగ్గరగా వెళ్లండి"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ఇక్కడ ప్లే చేయడానికి <xliff:g id="DEVICENAME">%1$s</xliff:g>కి దగ్గరగా వెళ్లండి"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ఇక్కడ ఆడటానికి, <xliff:g id="DEVICENAME">%1$s</xliff:g>‌కు దగ్గరగా వెళ్లండి"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే అవుతోంది"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"ఏదో తప్పు జరిగింది. మళ్లీ ట్రై చేయండి."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"లోడ్ అవుతోంది"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"టాబ్లెట్"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ఇన్‌యాక్టివ్, యాప్ చెక్ చేయండి"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"కనుగొనబడలేదు"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"కంట్రోల్ అందుబాటులో లేదు"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"ఎర్రర్, మళ్లీ ప్రయత్నించండి"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"కంట్రోల్స్‌ను జోడించండి"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"కంట్రోల్స్‌ను ఎడిట్ చేయండి"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"అవుట్‌పుట్‌లను జోడించండి"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"గ్రూప్"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 పరికరం ఎంచుకోబడింది"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"వాల్యూమ్"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"స్పీకర్‌లు &amp; డిస్‌ప్లేలు"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"సూచించబడిన పరికరాలు"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ప్రసారం కావడం అనేది ఎలా పని చేస్తుంది"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ప్రసారం"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"మీకు సమీపంలో ఉన్న వ్యక్తులు అనుకూలత ఉన్న బ్లూటూత్ పరికరాలతో మీరు ప్రసారం చేస్తున్న మీడియాను వినగలరు"</string>
@@ -1011,9 +1020,18 @@
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"ఇప్పుడే తిప్పండి"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"మెరుగైన సెల్ఫీ కోసం ఫోన్‌ను అన్‌ఫోల్డ్ చేయండి"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"మంచి సెల్ఫీ కోసం ముందు వైపు డిస్‌ప్లేకు తిప్పాలా?"</string>
-    <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"అధిక రిజల్యూషన్‌తో పెద్ద ఫోటో కోసం వెనుక వైపున ఉన్న కెమెరాను ఉపయోగించండి."</string>
+    <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"వెనుక వైపున ఉన్న కెమెరాను ఉపయోగించి అధిక రిజల్యూషన్ గల, మరింత వెడల్పైన ఫోటోను పొందండి."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ఈ స్క్రీన్ ఆఫ్ అవుతుంది"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"మడవగల పరికరం విప్పబడుతోంది"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"మడవగల పరికరం చుట్టూ తిప్పబడుతోంది"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> బ్యాటరీ మిగిలి ఉంది"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"మీ స్టైలస్‌ను ఛార్జర్‌కి కనెక్ట్ చేయండి"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"తక్కువ స్టైలస్ బ్యాటరీ"</string>
+    <string name="video_camera" msgid="7654002575156149298">"వీడియో కెమెరా"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"ఈ ప్రొఫైల్ నుండి కాల్ చేయడం సాధ్యపడలేదు"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"మీ వర్క్ పాలసీ, మిమ్మల్ని వర్క్ ప్రొఫైల్ నుండి మాత్రమే ఫోన్ కాల్స్ చేయడానికి అనుమతిస్తుంది"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"వర్క్ ప్రొఫైల్‌కు మారండి"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"మూసివేయండి"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index f8a16bd..28bb388 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ขอบเขตด้านล่าง <xliff:g id="PERCENT">%1$d</xliff:g> เปอร์เซ็นต์"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ขอบเขตด้านซ้าย <xliff:g id="PERCENT">%1$d</xliff:g> เปอร์เซ็นต์"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ขอบเขตด้านขวา <xliff:g id="PERCENT">%1$d</xliff:g> เปอร์เซ็นต์"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ภาพหน้าจองานจะบันทึกอยู่ในแอป <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ไฟล์"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"โปรแกรมบันทึกหน้าจอ"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"กำลังประมวลผลการอัดหน้าจอ"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"การแจ้งเตือนต่อเนื่องสำหรับเซสชันการบันทึกหน้าจอ"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"อัตโนมัติ"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"ไม่มีเสียงหรือการสั่น"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"ไม่มีเสียงหรือการสั่น และปรากฏต่ำลงมาในส่วนการสนทนา"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"อาจส่งเสียงหรือสั่นโดยขึ้นอยู่กับการตั้งค่าโทรศัพท์"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"อาจส่งเสียงหรือสั่นโดยขึ้นอยู่กับการตั้งค่าโทรศัพท์ การสนทนาจาก <xliff:g id="APP_NAME">%1$s</xliff:g> จะแสดงเป็นบับเบิลโดยค่าเริ่มต้น"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"อาจส่งเสียงหรือสั่นโดยขึ้นอยู่กับการตั้งค่าอุปกรณ์"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"อาจส่งเสียงหรือสั่นโดยขึ้นอยู่กับการตั้งค่าอุปกรณ์ การสนทนาจาก <xliff:g id="APP_NAME">%1$s</xliff:g> จะแสดงเป็นบับเบิลโดยค่าเริ่มต้น"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"ให้ระบบพิจารณาว่าจะให้การแจ้งเตือนนี้ส่งเสียงหรือสั่นหรือไม่"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;สถานะ:&lt;/b&gt; เลื่อนระดับเป็นค่าเริ่มต้น"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;สถานะ:&lt;/b&gt; ลดระดับเป็นปิดเสียง"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"เลือกแอปเพื่อเพิ่มตัวควบคุม"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{เพิ่มตัวควบคุม # ตัวแล้ว}other{เพิ่มตัวควบคุม # ตัวแล้ว}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"นำออกแล้ว"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"เพิ่ม <xliff:g id="APPNAME">%s</xliff:g> ไหม"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"เมื่อเพิ่ม <xliff:g id="APPNAME">%s</xliff:g> คุณจะเพิ่มการควบคุมและเนื้อหาไปยังแผงนี้ได้ ในบางแอป คุณเลือกได้ว่าต้องการให้การควบคุมใดปรากฏขึ้นที่นี่"</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"ตั้งเป็นรายการโปรดแล้ว"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"ตั้งเป็นรายการโปรดแล้ว โดยอยู่ลำดับที่ <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"นำออกจากรายการโปรดแล้ว"</string>
@@ -849,12 +853,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> จาก <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"เลิกทำ"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"ขยับเข้ามาใกล้ขึ้นเพื่อเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"ขยับไปใกล้ <xliff:g id="DEVICENAME">%1$s</xliff:g> มากขึ้นเพื่อเล่นที่นี่"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"ขยับไปใกล้มากขึ้นเพื่อเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ขยับไปใกล้ <xliff:g id="DEVICENAME">%1$s</xliff:g> มากขึ้นเพื่อเล่นที่นี่"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"กำลังเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"เกิดข้อผิดพลาด โปรดลองอีกครั้ง"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"กำลังโหลด"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"แท็บเล็ต"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"ไม่มีการใช้งาน โปรดตรวจสอบแอป"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"ไม่พบ"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"ใช้การควบคุมไม่ได้"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"พบข้อผิดพลาด โปรดลองอีกครั้ง"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"เพิ่มตัวควบคุม"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"แก้ไขตัวควบคุม"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"เพิ่มแอป"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"เพิ่มเอาต์พุต"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"กลุ่ม"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"เลือกอุปกรณ์ไว้ 1 รายการ"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"ระดับเสียง"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ลำโพงและจอแสดงผล"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"อุปกรณ์ที่แนะนำ"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"วิธีการทำงานของการออกอากาศ"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"ประกาศ"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ผู้ที่อยู่ใกล้คุณและมีอุปกรณ์บลูทูธที่รองรับสามารถรับฟังสื่อที่คุณกำลังออกอากาศได้"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ หน้าจอนี้จะปิดไป"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"อุปกรณ์ที่พับได้กำลังกางออก"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"อุปกรณ์ที่พับได้กำลังพลิกไปมา"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"เหลือแบตเตอรี่ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"เชื่อมต่อสไตลัสกับที่ชาร์จ"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"แบตเตอรี่สไตลัสเหลือน้อย"</string>
+    <string name="video_camera" msgid="7654002575156149298">"กล้องวิดีโอ"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"โทรจากโปรไฟล์นี้ไม่ได้"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"นโยบายการทำงานอนุญาตให้คุณโทรออกได้จากโปรไฟล์งานเท่านั้น"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"สลับไปใช้โปรไฟล์งาน"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"ปิด"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 3578aa7..7387289 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa ibaba"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa kaliwa"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa kanan"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Naka-save ang mga screenshot sa trabaho sa <xliff:g id="APP">%1$s</xliff:g> app"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Mga File"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Recorder ng Screen"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Pinoproseso screen recording"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Kasalukuyang notification para sa session ng pag-record ng screen"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Awtomatiko"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Walang tunog o pag-vibrate"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Walang tunog o pag-vibrate at lumalabas nang mas mababa sa seksyon ng pag-uusap"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Puwedeng mag-ring o mag-vibrate batay sa mga setting ng telepono"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Puwedeng mag-ring o mag-vibrate batay sa mga setting ng telepono. Mga pag-uusap mula sa <xliff:g id="APP_NAME">%1$s</xliff:g> bubble bilang default."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Puwedeng mag-ring o mag-vibrate batay sa mga setting ng device"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Puwedeng mag-ring o mag-vibrate batay sa mga setting ng device. Mga pag-uusap mula sa <xliff:g id="APP_NAME">%1$s</xliff:g> bubble bilang default."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Ipatukoy sa system kung dapat gumawa ng tunog o pag-vibrate ang notification na ito"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Status:&lt;/b&gt; Na-promote sa Default"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Status:&lt;/b&gt; Na-demote sa Naka-silent"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Pumili ng app para magdagdag ng mga kontrol"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Nagdagdag ng # kontrol.}one{Nagdagdag ng # kontrol.}other{Nagdagdag ng # na kontrol.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Inalis"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Idagdag ang <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Kapag idinagdag mo ang <xliff:g id="APPNAME">%s</xliff:g>, puwede itong magdagdag ng mga kontrol at content sa panel na ito. Sa ilang app, puwede mong piliin kung aling mga kontrol ang lalabas dito."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Ginawang paborito"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ginawang paborito, posisyon <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Inalis sa paborito"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"I-undo"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Lumapit pa para mag-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Lumapit sa <xliff:g id="DEVICENAME">%1$s</xliff:g> para mag-play rito"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para mag-play dito, lumapit sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Nagpe-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Nagkaproblema. Subukan ulit."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Naglo-load"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Hindi aktibo, tingnan ang app"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Hindi nahanap"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Hindi available ang kontrol"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Nagka-error, subukan ulit"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Magdagdag ng mga kontrol"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Mag-edit ng mga kontrol"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Magdagdag ng app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Magdagdag ng mga output"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device ang napili"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Mga Speaker at Display"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Mga Iminumungkahing Device"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Paano gumagana ang pag-broadcast"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Makakapakinig ang mga taong malapit sa iyo na may mga compatible na Bluetooth device sa media na bino-broadcast mo"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Mag-o-off ang screen na ito"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ina-unfold na foldable na device"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Fini-flip na foldable na device"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> baterya na lang ang natitira"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ikonekta sa charger ang iyong stylus"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Paubos na ang baterya ng stylus"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Video camera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Hindi puwedeng tumawag mula sa profile na ito"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Pinapayagan ka ng iyong patakaran sa trabaho na tumawag lang mula sa profile sa trabaho"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Lumipat sa profile sa trabaho"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Isara"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 64431ad2..7c5552d 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alt sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"İş profilindeki ekran görüntüleri <xliff:g id="APP">%1$s</xliff:g> uygulamasına kaydedilir"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dosyalar"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Ekran Kaydedicisi"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekran kaydı işleniyor"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekran kaydı oturumu için devam eden bildirim"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Otomatik"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Sessiz veya titreşim yok"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Ses veya titreşim yok, görüşme bölümünün altında görünür"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Telefon ayarlarına bağlı olarak zili çalabilir veya titreyebilir"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Telefon ayarlarına bağlı olarak zili çalabilir veya titreyebilir <xliff:g id="APP_NAME">%1$s</xliff:g> adlı uygulamadan görüşmeler varsayılan olarak baloncukla gösterilir."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Cihaz ayarlarına bağlı olarak zili çalabilir veya titreyebilir"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Cihaz ayarlarına bağlı olarak zili çalabilir veya titreyebilir <xliff:g id="APP_NAME">%1$s</xliff:g> adlı uygulamadan görüşmeler varsayılan olarak baloncukla gösterilir."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Bu bildirimin ses çıkarması veya titreşmesi gerekip gerekmediğine sistem karar versin"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Durum:&lt;/b&gt; Varsayılana yükseltildi"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Durum:&lt;/b&gt; Sessize Düşürüldü"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Denetim eklemek için uygulama seçin"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrol eklendi.}other{# kontrol eklendi.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Kaldırıldı"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favoriler listesine eklendi"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favorilere eklendi, konum: <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Favorilerden kaldırıldı"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> uygulamasından <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Geri al"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında çalmak için yaklaşın"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Burada oynatmak için <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaklaşın"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oynatmak için yaklaşın"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Burada oynatmak için <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaklaşın"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oynatılıyor"</string>
-    <string name="media_transfer_failed" msgid="7955354964610603723">"Bir sorun oldu. Tekrar deneyin."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_failed" msgid="7955354964610603723">"Bir hata oluştu. Tekrar deneyin."</string>
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Yükleme"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Devre dışı, uygulamaya bakın"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Bulunamadı"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol kullanılamıyor"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Hata, yeniden deneyin"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Denetim ekle"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Denetimleri düzenle"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Çıkışlar ekleyin"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçildi"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ses düzeyi"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hoparlörler ve Ekranlar"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Önerilen Cihazlar"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayınlamanın işleyiş şekli"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Anons"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Yakınınızda ve uyumlu Bluetooth cihazları olan kişiler yayınladığınız medya içeriğini dinleyebilir"</string>
@@ -1012,8 +1021,17 @@
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Daha iyi selfie çekmek için telefonu açın"</string>
     <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Daha iyi bir selfie için ön ekrana geçilsin mi?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Daha yüksek çözünürlüğe sahip daha büyük bir fotoğraf için arka yüz kamerasını kullanın."</string>
-    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ * Bu ekran kapatılacak"</b></string>
+    <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Bu ekran kapatılacak"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Katlanabilir cihaz açılıyor"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Katlanabilir cihaz döndürülüyor"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> pil kaldı"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ekran kaleminizi bir şarj cihazına bağlayın"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Ekran kaleminin pil seviyesi düşük"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Video kamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Bu profilden telefon araması yapılamıyor"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"İşletme politikanız yalnızca iş profilinden telefon araması yapmanıza izin veriyor"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"İş profiline geç"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Kapat"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 9eba610..0a93ec6 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Знизу на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Зліва на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Справа на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Робочі знімки екрана зберігаються в додатку <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файли"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Запис відео з екрана"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обробка записування екрана"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Сповіщення про сеанс запису екрана"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Автоматично"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Без звуку чи вібрації"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Без звуку чи вібрації, з\'являється нижче в розділі розмов"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Дзвінок або вібрація залежно від налаштувань телефона"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Дзвінок або вібрація залежно від налаштувань телефона. Розмови з додатка <xliff:g id="APP_NAME">%1$s</xliff:g> за умовчанням з\'являються як спливаючий чат."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Дзвінок або вібрація залежно від налаштувань пристрою"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Може дзвонити або вібрувати залежно від налаштувань пристрою. Показує спливаючі розмови з додатка <xliff:g id="APP_NAME">%1$s</xliff:g> за умовчанням."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Дозволити системі визначати, чи має сповіщення супроводжуватися звуком або вібрацією"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Статус&lt;/b&gt;: підвищено до \"За умовчанням\""</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Статус&lt;/b&gt;: знижено до \"Без звуку\""</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Виберіть, для якого додатка налаштувати елементи керування"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Додано # елемент керування.}one{Додано # елемент керування.}few{Додано # елементи керування.}many{Додано # елементів керування.}other{Додано # елемента керування.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Вилучено"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Додано у вибране"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Додано у вибране, позиція <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Видалено з вибраного"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" у додатку <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Відмінити"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Щоб відтворити контент на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>, наблизьтеся до нього"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Наблизьтеся до пристрою <xliff:g id="DEVICENAME">%1$s</xliff:g>, щоб відтворити медіафайли на ньому"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Щоб відтворити на цьому пристрої, перемістіть його ближче до пристрою \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Відтворюється на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Сталася помилка. Повторіть спробу."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Завантаження"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, перейдіть у додаток"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Не знайдено"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Елемент керування недоступний"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Помилка. Спробуйте знову"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Додати елементи керування"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Змінити елементи керування"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додати пристрої виводу"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Вибрано 1 пристрій"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Гучність"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Колонки й екрани"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Пропоновані пристрої"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як працює трансляція"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Трансляція"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Люди поблизу, які мають сумісні пристрої з Bluetooth, можуть слухати медіаконтент, який ви транслюєте."</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Цей екран вимкнеться"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Розкладний пристрій у розкладеному стані"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Розкладний пристрій обертається"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Заряд акумулятора: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Підключіть стилус до зарядного пристрою"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Низький заряд акумулятора стилуса"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Відеокамера"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Неможливо телефонувати з цього профілю"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Відповідно до правил організації ви можете телефонувати лише з робочого профілю"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Перейти в робочий профіль"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Закрити"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index cfcdeca3a..ad845f0 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"نیچے کا احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"بایاں احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"دایاں احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"دفتری اسکرین شاٹس کو <xliff:g id="APP">%1$s</xliff:g> ایپ میں محفوظ کیا جاتا ہے"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"فائلز"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"اسکرین ریکارڈر"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"سکرین ریکارڈنگ پروسیس ہورہی ہے"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"اسکرین ریکارڈ سیشن کیلئے جاری اطلاع"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"خودکار"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"کوئی آواز یا وائبریشن نہیں"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"کوئی آواز یا وائبریشن نہیں اور گفتگو کے سیکشن میں نیچے ظاہر ہوتا ہے"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"آپ کے آلہ کی ترتیبات کے مطابق وائبریٹ یا گھنٹی بج سکتی ہے"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"فون کی ترتیبات کے مطابق وائبریٹ یا گھنٹی بج سکتی ہے۔ بذریعہ ڈیفالٹ <xliff:g id="APP_NAME">%1$s</xliff:g> بلبلہ سے گفتگوئیں۔"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"آلے کی ترتیبات کی بنیاد پر وائبریٹ یا گھنٹی بج سکتی ہے"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"آلے کی ترتیبات بنیاد پر وائبریٹ یا گھنٹی بج سکتی ہے۔ بذریعہ ڈیفالٹ <xliff:g id="APP_NAME">%1$s</xliff:g> بلبلہ سے گفتگوئیں۔"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"سسٹم کو اس بات کا تعین کرنے دیں کہ آیا اس اطلاع کی آواز ہو یا وائبریٹ ہونا چاہیے"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"‏&lt;b&gt;Status:&lt;/b&gt; ڈیفالٹ پر درجہ بند کیا گیا"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"‏&lt;b&gt;اسٹیٹس:&lt;/b&gt; کو خاموش پر درجہ بند کیا گیا"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"کنٹرولز شامل کرنے کے لیے ایپ منتخب کریں"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# کنٹرول کو شامل کیا گیا۔}other{# کنٹرولز کو شامل کیا گیا۔}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"ہٹا دیا گیا"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> کو شامل کریں؟"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"جب آپ <xliff:g id="APPNAME">%s</xliff:g> کو شامل کرتے ہیں، تو یہ اس پینل میں کنٹرولز اور مواد کو شامل کر سکتا ہے۔ کچھ ایپس میں، آپ یہ منتخب کر سکتے ہیں کہ کون سے کنٹرولز یہاں ظاہر ہوں۔"</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"پسند کردہ"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"پسند کردہ، پوزیشن <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ناپسند کردہ"</string>
@@ -849,12 +853,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> سے <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"کالعدم کریں"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چلانے کے لیے قریب کریں"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"یہاں چلانے کے ليے <xliff:g id="DEVICENAME">%1$s</xliff:g> کے قریب جائیں"</string>
-    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چل رہا ہے"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"‫<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چلانے کے لیے قریب کریں"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"یہاں چلانے کے لیے، <xliff:g id="DEVICENAME">%1$s</xliff:g> کے زیادہ جائیں"</string>
+    <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"‫<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چل رہا ہے"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"کچھ غلط ہوگیا۔ پھر کوشش کریں۔"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"لوڈ ہو رہا ہے"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ٹیبلیٹ"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"غیر فعال، ایپ چیک کریں"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"نہیں ملا"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"کنٹرول دستیاب نہیں ہے"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"خرابی، دوبارہ کوشش کریں"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"کنٹرولز شامل کریں"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"کنٹرولز میں ترمیم کریں"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ایپ شامل کریں"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"آؤٹ پٹس شامل کریں"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"گروپ"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 آلہ منتخب کیا گیا"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"والیوم"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"اسپیکرز اور ڈسپلیز"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"تجویز کردہ آلات"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"براڈکاسٹنگ کیسے کام کرتا ہے"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"براڈکاسٹ"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"موافق بلوٹوتھ آلات کے ساتھ آپ کے قریبی لوگ آپ کے نشر کردہ میڈیا کو سن سکتے ہیں"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ یہ اسکرین آف ہو جائے گی"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"فولڈ ہونے والے آلے کو کھولا جا رہا ہے"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"فولڈ ہونے والے آلے کو گھمایا جا رہا ہے"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> بیٹری باقی ہے"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"اپنے اسٹائلس کو چارجر منسلک کریں"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"اسٹائلس بیٹری کم ہے"</string>
+    <string name="video_camera" msgid="7654002575156149298">"ویڈیو کیمرا"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"اس پروفائل سے کال نہیں کر سکتے"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"آپ کے کام سے متعلق پالیسی آپ کو صرف دفتری پروفائل سے فون کالز کرنے کی اجازت دیتی ہے"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"دفتری پروفائل پر سوئچ کریں"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"بند کریں"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 8308519..4927ec2 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Quyi chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Chap chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Oʻng chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ish skrinshotlari <xliff:g id="APP">%1$s</xliff:g> ilovasida saqlanadi"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fayllar"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Ekrandan yozib olish"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekran yozib olinmoqda"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekrandan yozib olish seansi uchun joriy bildirishnoma"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Avtomatik"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Tovush yoki tebranishsiz"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Tovush yoki tebranishsiz hamda suhbatlar ruknining pastida chiqadi"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Telefon sozlamalari asosida jiringlashi yoki tebranishi mumkin"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Telefon sozlamalari asosida jiringlashi yoki tebranishi mumkin. <xliff:g id="APP_NAME">%1$s</xliff:g> suhbatlari standart holatda bulutcha shaklida chiqadi."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Qurilma sozlamalari asosida jiringlashi yoki tebranishi mumkin"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Qurilma sozlamalari asosida jiringlashi yoki tebranishi mumkin. <xliff:g id="APP_NAME">%1$s</xliff:g> suhbatlari standart holatda bulutcha shaklida chiqadi."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Bu bildirishnoma jiringlashi yoki tebranishini hal qilsin"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Holati:&lt;/b&gt; Birlamchi darajaga chiqarildi"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Holati:&lt;/b&gt; Sokin darajaga tushirildi"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Boshqaruv elementlarini kiritish uchun ilovani tanlang"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ta boshqaruv elementi kiritildi.}other{# ta boshqaruv elementi kiritildi.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Olib tashlandi"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Saralanganlarga kiritilgan"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Saralanganlarga kiritilgan, <xliff:g id="NUMBER">%d</xliff:g>-joy"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Saralanganlardan olib tashlangan"</string>
@@ -849,12 +855,12 @@
     <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etish: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ilovasida ijro etilmoqda: <xliff:g id="SONG_NAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Qaytarish"</string>
-    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>da ijro etish uchun yaqinroq keling"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Bu yerda ijro qilish uchun <xliff:g id="DEVICENAME">%1$s</xliff:g>qurilmasiga yaqinlashtiring"</string>
+    <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> qurilmasida ijro qilish uchun unga yaqinlashing"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Shu yerda ijro qilish uchun <xliff:g id="DEVICENAME">%1$s</xliff:g>ga yaqinroq suring"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"<xliff:g id="DEVICENAME">%1$s</xliff:g> qurilmasida ijro qilinmoqda"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Xatolik yuz berdi. Qayta urining."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Yuklanmoqda"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"planshet"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Nofaol. Ilovani tekshiring"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Topilmadi"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Boshqarish imkonsiz"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Xato, qayta urining"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Element kiritish"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Elementlarni tahrirlash"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Chiquvchi qurilmani kiritish"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Guruh"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ta qurilma tanlandi"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Tovush balandligi"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Karnaylar va displeylar"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Taklif qilingan qurilmalar"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Translatsiya qanday ishlaydi"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Translatsiya"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Atrofingizdagi mos Bluetooth qurilmasiga ega foydalanuvchilar siz translatsiya qilayotgan mediani tinglay olishadi"</string>
@@ -1008,12 +1017,21 @@
     <string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Kamida bitta qurilma mavjud"</string>
     <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Bosib turish yorligʻi"</string>
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Bekor qilish"</string>
-    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Hozir aylantirish"</string>
+    <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Almashtirish"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Yaxshiroq selfi olish uchun telefonni yoying"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Old ekran sizga qaragan holda aylantirdingizmi?"</string>
-    <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Keng va yuqori tiniqlikdagi suratga olish uchun orqa kameradan foydalaning."</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Yaxshiroq selfi uchun old ekranga almashilsinmi?"</string>
+    <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Keng burchakli va yuqori aniqlikda suratga olish uchun orqa kameradan foydalaning."</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Bu ekran oʻchiriladi"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Buklanadigan qurilma ochilmoqda"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Buklanadigan qurilma aylantirilmoqda"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batareya quvvati: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Stilusni quvvat manbaiga ulang"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Stilus batareyasi kam"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Videokamera"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Bu profildan chaqiruv qilish imkonsiz"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Ishga oid siyosatingiz faqat ish profilidan telefon chaqiruvlarini amalga oshirish imkonini beradi"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Ish profiliga almashish"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Yopish"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 80aa822..016c834 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Cạnh dưới cùng <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Cạnh trái <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Cạnh phải <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ảnh chụp màn hình công việc được lưu trong ứng dụng <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Trình ghi màn hình"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Đang xử lý video ghi màn hình"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Thông báo đang diễn ra về phiên ghi màn hình"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Tự động"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Không phát âm thanh hoặc rung"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Không phát âm thanh hoặc rung và xuất hiện phía dưới trong phần cuộc trò chuyện"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Có thể đổ chuông hoặc rung tùy theo chế độ cài đặt trên điện thoại"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Có thể đổ chuông hoặc rung tùy theo chế độ cài đặt trên điện thoại. Các cuộc trò chuyện từ <xliff:g id="APP_NAME">%1$s</xliff:g> sẽ hiện ở dạng bong bóng theo mặc định."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Có thể đổ chuông hoặc rung tuỳ theo chế độ cài đặt trên thiết bị"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Có thể đổ chuông hoặc rung tuỳ theo chế độ cài đặt trên thiết bị. Theo mặc định, các cuộc trò chuyện từ <xliff:g id="APP_NAME">%1$s</xliff:g> sẽ hiển thị dưới dạng bong bóng."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Cho phép hệ thống quyết định xem thông báo này phát âm thanh hay rung"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Trạng thái:&lt;/b&gt; Đã thay đổi thành Mặc định"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Trạng thái:&lt;/b&gt; Đã thay đổi thành Im lặng"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Chọn ứng dụng để thêm các tùy chọn điều khiển"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Đã thêm # chế độ điều khiển.}other{Đã thêm # chế độ điều khiển.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Đã xóa"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Được yêu thích"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Được yêu thích, vị trí số <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Không được yêu thích"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> trên <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Hủy"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Đưa thiết bị đến gần hơn để phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Di chuyển đến gần <xliff:g id="DEVICENAME">%1$s</xliff:g> hơn để phát tại đây"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Để phát ở đây, vui lòng lại gần <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Đang phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Đã xảy ra lỗi. Hãy thử lại."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Đang tải"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"máy tính bảng"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Không hoạt động, hãy kiểm tra ứng dụng"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Không tìm thấy"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Không có chức năng điều khiển"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Lỗi, hãy thử lại"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Thêm các tùy chọn điều khiển"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Chỉnh sửa tùy chọn điều khiển"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Thêm thiết bị đầu ra"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Nhóm"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"Đã chọn 1 thiết bị"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Âm lượng"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Loa và màn hình"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Thiết bị được đề xuất"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cách tính năng truyền hoạt động"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Truyền"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Những người ở gần có thiết bị Bluetooth tương thích có thể nghe nội dung nghe nhìn bạn đang truyền"</string>
@@ -1015,5 +1024,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Màn hình này sẽ tắt"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Thiết bị có thể gập lại đang được mở ra"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Thiết bị có thể gập lại đang được lật ngược"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Còn <xliff:g id="PERCENTAGE">%s</xliff:g> pin"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Hãy kết nối bút cảm ứng với bộ sạc"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Bút cảm ứng bị yếu pin"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Máy quay video"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Không thể gọi điện từ hồ sơ này"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Chính sách của nơi làm việc chỉ cho phép bạn gọi điện thoại từ hồ sơ công việc"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Chuyển sang hồ sơ công việc"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Đóng"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index a449e9e..15c834c 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"底部边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"工作屏幕截图保存在“<xliff:g id="APP">%1$s</xliff:g>”应用中"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"文件"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"屏幕录制器"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"正在处理屏幕录制视频"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"持续显示屏幕录制会话通知"</string>
@@ -374,8 +376,8 @@
     <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允许 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或录制吗?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整个屏幕"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"单个应用"</string>
-    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"当您进行分享、录制或投屏时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问您的屏幕上显示的或设备上播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"当您对一款应用进行分享、录制或投屏时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问该应用中显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"继续"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或录制应用"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"是否允许此应用进行分享或录制?"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"自动"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"不发出提示音,也不振动"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"不发出提示音,也不振动;显示在对话部分的靠下位置"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"可能会响铃或振动(取决于手机设置)"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"可能会响铃或振动(取决于手机设置)。默认情况下,来自<xliff:g id="APP_NAME">%1$s</xliff:g>的对话会以对话泡的形式显示。"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"可能会响铃或振动,取决于设备设置"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"可能会响铃或振动,取决于设备设置。默认情况下,来自<xliff:g id="APP_NAME">%1$s</xliff:g>的对话会以对话泡的形式显示。"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"让系统决定是否应让设备在收到此通知时发出提示音或振动"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;状态&lt;/b&gt;:已提升为“默认”"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;状态&lt;/b&gt;:已降低为“静音”"</string>
@@ -798,6 +800,10 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"选择要添加控制器的应用"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{已添加 # 个控件。}other{已添加 # 个控件。}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"已移除"</string>
+    <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
+    <skip />
+    <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
+    <skip />
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"已收藏"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"已收藏,位置:<xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"已取消收藏"</string>
@@ -850,11 +856,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"通过<xliff:g id="APP_LABEL">%2$s</xliff:g>播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"撤消"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"若要在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放,请靠近这台设备"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"若要在此设备上播放,请靠近“<xliff:g id="DEVICENAME">%1$s</xliff:g>”"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"若要在此设备上播放,请再靠近<xliff:g id="DEVICENAME">%1$s</xliff:g>一点"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"正在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"出了点问题,请重试。"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"正在加载"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"平板电脑"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"无效,请检查应用"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"未找到"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"控件不可用"</string>
@@ -864,6 +870,8 @@
     <string name="controls_error_failed" msgid="960228639198558525">"出现错误,请重试"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"添加控制器"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"修改控制器"</string>
+    <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
+    <skip />
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"添加输出设备"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"群组"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已选择 1 个设备"</string>
@@ -878,6 +886,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"音量"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"音箱和显示屏"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建议的设备"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"广播的运作方式"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"广播"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"附近使用兼容蓝牙设备的用户可以收听您广播的媒体内容"</string>
@@ -1010,10 +1019,19 @@
     <string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"取消"</string>
     <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"立即翻转"</string>
     <string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"展开手机可拍出更好的自拍照"</string>
-    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"要翻转到外屏以拍出更好的自拍照吗?"</string>
+    <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"翻转到外屏后自拍效果更好,要试试吗?"</string>
     <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"您可以使用后置摄像头拍摄视角更广、分辨率更高的照片。"</string>
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ 此屏幕将会关闭"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展开可折叠设备"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻转可折叠设备"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"电池还剩 <xliff:g id="PERCENTAGE">%s</xliff:g> 的电量"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"请将触控笔连接充电器"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"触控笔电池电量低"</string>
+    <string name="video_camera" msgid="7654002575156149298">"摄像机"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"无法通过这份资料拨打电话"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"根据您的工作政策,您只能通过工作资料拨打电话"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"切换到工作资料"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"关闭"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 4749e3b..8aa29763 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -91,13 +91,15 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"下方邊界 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左方邊界 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右方邊界 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"工作的螢幕截圖會儲存在「<xliff:g id="APP">%1$s</xliff:g>」應用程式"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"檔案"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"螢幕畫面錄影工具"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"正在處理螢幕錄影內容"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"持續顯示錄影畫面工作階段通知"</string>
     <string name="screenrecord_start_label" msgid="1750350278888217473">"要開始錄製嗎?"</string>
     <string name="screenrecord_description" msgid="1123231719680353736">"錄影時,Android 系統可擷取螢幕上顯示或裝置播放的任何敏感資料,包括密碼、付款資料、相片、訊息和音訊。"</string>
     <string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"錄製整個螢幕畫面"</string>
-    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"錄製單一應用程式"</string>
+    <string name="screenrecord_option_single_app" msgid="5954863081500035825">"錄製一個應用程式"</string>
     <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"進行錄製時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
     <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"錄製應用程式時,Android 可存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
     <string name="screenrecord_start_recording" msgid="348286842544768740">"開始錄製"</string>
@@ -373,8 +375,8 @@
     <string name="media_projection_dialog_title" msgid="3316063622495360646">"要使用「<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>」開始錄影或投放嗎?"</string>
     <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製嗎?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整個螢幕畫面"</string>
-    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"單一應用程式"</string>
-    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+    <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"一個應用程式"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"當您分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取在螢幕畫面上顯示或在裝置上播放的所有內容。因此請小心保管密碼、付款資料、訊息或其他敏感資料。"</string>
     <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"自動"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"無音效或震動"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"無音效或震動,並在對話部分的較低位置顯示"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"根據手機設定發出鈴聲或震動"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"可能會根據手機設定發出鈴聲或震動。「<xliff:g id="APP_NAME">%1$s</xliff:g>」的對話會預設以對話氣泡顯示。"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"可能會根據裝置設定發出鈴聲或震動"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"可能會根據裝置設定發出鈴聲或震動。根據預設,來自 <xliff:g id="APP_NAME">%1$s</xliff:g> 的對話會以對話氣泡顯示。"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"由系統判斷是否要讓此通知發出音效或震動"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;狀態:&lt;/b&gt;已提升為預設"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;狀態:&lt;/b&gt;已降低為靜音"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"選擇要新增控制項的應用程式"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{已新增 # 個控制項。}other{已新增 # 個控制項。}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"已移除"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"要新增「<xliff:g id="APPNAME">%s</xliff:g>」嗎?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"當你新增「<xliff:g id="APPNAME">%s</xliff:g>」時,應用程式也可將控制選項及內容新增到這個面板。某些應用程式可讓你選擇要顯示在這裡的控制選項。"</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"已加入收藏"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"已加入至收藏位置 <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"已取消收藏"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"在 <xliff:g id="APP_LABEL">%2$s</xliff:g> 播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近一點"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"如要在此裝置上播放,請靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"如要在這部裝置播放,請靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」一點"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"正在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"發生錯誤,請再試一次。"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"正在載入"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"平板電腦"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"已停用,請檢查應用程式"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"找不到"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"無法使用控制功能"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"發生錯誤,請重試"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"新增控制項"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"編輯控制項"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"新增應用程式"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"新增輸出裝置"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"音量"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"喇叭和螢幕"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建議的裝置"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"廣播運作方式"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"廣播"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"附近有兼容藍牙裝置的人可收聽您正在廣播的媒體內容"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ 此螢幕將關閉"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開折疊式裝置"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"剩餘電量:<xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"將觸控筆連接充電器"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"觸控筆電量不足"</string>
+    <string name="video_camera" msgid="7654002575156149298">"攝影機"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"無法透過此設定檔撥打電話"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"您的公司政策只允許透過工作設定檔撥打電話"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"切換至工作設定檔"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"關閉"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 4972aee..321821c 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"下方邊界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左側邊界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右側邊界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"工作的螢幕截圖會儲存在「<xliff:g id="APP">%1$s</xliff:g>」應用程式"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"檔案"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"螢幕錄影器"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"處理螢幕錄影內容"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"持續顯示螢幕畫面錄製工作階段通知"</string>
@@ -374,8 +376,8 @@
     <string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"允許 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 分享或錄製?"</string>
     <string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"整個螢幕畫面"</string>
     <string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"單一應用程式"</string>
-    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
-    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+    <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+    <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取在應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
     <string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
     <string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
     <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"要允許這個應用程式分享或錄製嗎?"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"自動"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"不震動或發出聲音"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"不震動或發出聲音,並顯示在對話區的下方"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"根據手機的設定響鈴或震動"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"可能會根據手機的設定響鈴或震動。根據預設,來自「<xliff:g id="APP_NAME">%1$s</xliff:g>」的對話會以對話框形式顯示。"</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"根據裝置的設定響鈴或震動"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"根據裝置的設定響鈴或震動。根據預設,來自「<xliff:g id="APP_NAME">%1$s</xliff:g>」的對話會以對話框形式顯示。"</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"由系統判斷要讓裝置在收到這則通知時震動還是發出音效"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;狀態:&lt;/b&gt;已提升為預設"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;狀態:&lt;/b&gt;已降低為靜音"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"選擇應用程式以新增控制項"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{已新增 # 個控制項。}other{已新增 # 個控制項。}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"已移除"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"要新增「<xliff:g id="APPNAME">%s</xliff:g>」嗎?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"當你新增「<xliff:g id="APPNAME">%s</xliff:g>」時,應用程式也可將控制選項及內容新增到這個面板。某些應用程式可讓你選擇要顯示在這裡的控制選項。"</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"已加入收藏"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"已加入收藏,位置 <xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"從收藏中移除"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"透過「<xliff:g id="APP_LABEL">%2$s</xliff:g>」播放〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近一點"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"如要在這部裝置上播放,請移到更靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」的位置"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"如要在這部裝置播放,請移到更靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」的位置"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"正在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"發生錯誤,請再試一次。"</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"載入中"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"平板電腦"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"無效,請查看應用程式"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"找不到控制項"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"無法使用控制項"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"發生錯誤,請再試一次"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"新增控制項"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"編輯控制項"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"新增應用程式"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"新增輸出裝置"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"群組"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"已選取 1 部裝置"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"音量"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"喇叭和螢幕"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建議的裝置"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"廣播功能的運作方式"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"廣播"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"如果附近的人有相容的藍牙裝置,就可以聽到你正在廣播的媒體內容"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ 這麼做會關閉這個螢幕"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開的折疊式裝置"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"剩餘電量:<xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"將觸控筆接上充電器"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"觸控筆電力不足"</string>
+    <string name="video_camera" msgid="7654002575156149298">"攝影機"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"無法透過這個資料夾撥打電話"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"貴公司政策僅允許透過工作資料夾撥打電話"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"切換至工作資料夾"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"關閉"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index d9cf44b..c094acf 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -91,6 +91,8 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ophansi"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ongakwesobunxele"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ongakwesokudla"</string>
+    <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Izithombe-skrini zomsebenzi zigcinwa ku-app ye-<xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Amafayela"</string>
     <string name="screenrecord_name" msgid="2596401223859996572">"Irekhoda yesikrini"</string>
     <string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Icubungula okokuqopha iskrini"</string>
     <string name="screenrecord_channel_description" msgid="4147077128486138351">"Isaziso esiqhubekayo seseshini yokurekhoda isikrini"</string>
@@ -530,8 +532,8 @@
     <string name="notification_automatic_title" msgid="3745465364578762652">"Okuzenzekelayo"</string>
     <string name="notification_channel_summary_low" msgid="4860617986908931158">"Awukho umsindo noma ukudlidliza"</string>
     <string name="notification_conversation_summary_low" msgid="1734433426085468009">"Awukho umsindo noma ukudlidliza futhi ivela ngezansi esigabeni sengxoxo"</string>
-    <string name="notification_channel_summary_default" msgid="3282930979307248890">"Ingase ikhale noma idlidlize kuya ngamasethingi wefoni yakho"</string>
-    <string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"Ingase ikhale noma idlidlize kuya ngamasethingi wefoni yakho. Izingxoxo ezivela ku-<xliff:g id="APP_NAME">%1$s</xliff:g> ziba yibhamuza ngokuzenzakalela."</string>
+    <string name="notification_channel_summary_default" msgid="777294388712200605">"Ingase ikhale noma idlidlize ngokusekelwe kumasethingi edivayisi"</string>
+    <string name="notification_channel_summary_default_with_bubbles" msgid="3482483084451555344">"Ingase ikhale noma idlidlize kuya ngamasethingi wedivayisi. Izingxoxo ezivela ku-<xliff:g id="APP_NAME">%1$s</xliff:g> ziba yibhamuza ngokuzenzakalela."</string>
     <string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Vumela isistimu inqume uma lesi saziso kufanele senze umsindo noma sidlidlize"</string>
     <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"&lt;b&gt;Isimo:&lt;/b&gt; Siphromothelwe Kokuzenzakalelayo"</string>
     <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"&lt;b&gt;Isimo:&lt;/b&gt; Sehliselwe Kokuthulile"</string>
@@ -798,6 +800,8 @@
     <string name="controls_providers_title" msgid="6879775889857085056">"Khetha uhlelo lokusebenza ukwengeza izilawuli"</string>
     <string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{ulawulo olu-# olwengeziwe.}one{ukulawulwa okungu-# okwengeziwe.}other{ukulawulwa okungu-# okwengeziwe.}}"</string>
     <string name="controls_removed" msgid="3731789252222856959">"Isusiwe"</string>
+    <string name="controls_panel_authorization_title" msgid="267429338785864842">"Engeza i-<xliff:g id="APPNAME">%s</xliff:g>?"</string>
+    <string name="controls_panel_authorization" msgid="4540047176861801815">"Uma wengeza i-<xliff:g id="APPNAME">%s</xliff:g>, ingangeza izilawuli nokuqukethwe kuleli phaneli. Kwamanye ama-app, ungakhetha ukuthi yiziphi izilawuli eziboniswa lapha."</string>
     <string name="accessibility_control_favorite" msgid="8694362691985545985">"Kwenziwe intandokazi"</string>
     <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Kwenziwe intandokazi, isimo esiyi-<xliff:g id="NUMBER">%d</xliff:g>"</string>
     <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Akwenziwanga intandokazi"</string>
@@ -850,11 +854,11 @@
     <string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
     <string name="media_transfer_undo" msgid="1895606387620728736">"Hlehlisa"</string>
     <string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Sondeza eduze ukudlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
-    <string name="media_move_closer_to_end_cast" msgid="6495907340926563656">"Sondela eduze ne-<xliff:g id="DEVICENAME">%1$s</xliff:g> ukuze udlale lapha"</string>
+    <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ukuze udlale lapha, sondela ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Idlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
     <string name="media_transfer_failed" msgid="7955354964610603723">"Kukhona okungahambanga kahle. Zama futhi."</string>
-    <!-- no translation found for media_transfer_loading (5544017127027152422) -->
-    <skip />
+    <string name="media_transfer_loading" msgid="5544017127027152422">"Iyalayisha"</string>
+    <string name="media_ttt_default_device_type" msgid="4457646436153370169">"ithebulethi"</string>
     <string name="controls_error_timeout" msgid="794197289772728958">"Akusebenzi, hlola uhlelo lokusebenza"</string>
     <string name="controls_error_removed" msgid="6675638069846014366">"Ayitholakali"</string>
     <string name="controls_error_removed_title" msgid="1207794911208047818">"Ukulawula akutholakali"</string>
@@ -864,6 +868,7 @@
     <string name="controls_error_failed" msgid="960228639198558525">"Iphutha, zama futhi"</string>
     <string name="controls_menu_add" msgid="4447246119229920050">"Engeza Izilawuli"</string>
     <string name="controls_menu_edit" msgid="890623986951347062">"Hlela izilawuli"</string>
+    <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Engeza i-app"</string>
     <string name="media_output_dialog_add_output" msgid="5642703238877329518">"Engeza okukhiphayo"</string>
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Iqembu"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"idivayisi ekhethiwe e-1"</string>
@@ -878,6 +883,7 @@
     <string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Ivolumu"</string>
     <string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
     <string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Izipikha Neziboniso"</string>
+    <string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Amadivayisi Aphakanyisiwe"</string>
     <string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Indlela ukusakaza okusebenza ngayo"</string>
     <string name="media_output_broadcast" msgid="3555580945878071543">"Sakaza"</string>
     <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Abantu abaseduze nawe abanamadivayisi e-Bluetooth ahambisanayo bangalalela imidiya oyisakazayo"</string>
@@ -1015,5 +1021,14 @@
     <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Lesi sikrini sizovala"</b></string>
     <string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Idivayisi egoqekayo iyembulwa"</string>
     <string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Idivayisi egoqekayo iphendulwa nxazonke"</string>
+    <string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ibhethri elisele"</string>
+    <string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Xhuma i-stylus yakho kushaja"</string>
     <string name="stylus_battery_low" msgid="7134370101603167096">"Ibhethri le-stylus liphansi"</string>
+    <string name="video_camera" msgid="7654002575156149298">"Ikhamera yevidiyo"</string>
+    <string name="call_from_work_profile_title" msgid="6991157106804289643">"Ayikwazi ukufonela le phrofayela"</string>
+    <string name="call_from_work_profile_text" msgid="3458704745640229638">"Inqubomgomo yakho yomsebenzi ikuvumela ukuthi wenze amakholi wefoni kuphela ngephrofayela yomsebenzi"</string>
+    <string name="call_from_work_profile_action" msgid="2937701298133010724">"Shintshela kuphrofayela yomsebenzi"</string>
+    <string name="call_from_work_profile_close" msgid="7927067108901068098">"Vala"</string>
+    <!-- no translation found for lock_screen_settings (9197175446592718435) -->
+    <skip />
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e8a5e7e..371f001 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -824,4 +824,12 @@
         <item>bottom_end:wallet</item>
     </string-array>
 
+    <!-- Package name for the app that implements the wallpaper picker. -->
+    <string name="config_wallpaperPickerPackage" translatable="false">
+        com.android.wallpaper
+    </string>
+    
+    <!-- Whether the floating rotation button should be on the left/right in the device's natural
+         orientation -->
+    <bool name="floating_rotation_button_position_left">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 890d964..d492e53c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -43,12 +43,18 @@
     <dimen name="navigation_edge_panel_height">268dp</dimen>
     <!-- The threshold to drag to trigger the edge action -->
     <dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
+    <!-- The drag distance to consider evaluating gesture -->
+    <dimen name="navigation_edge_action_min_distance_to_start_animation">24dp</dimen>
     <!-- The threshold to progress back animation for edge swipe -->
     <dimen name="navigation_edge_action_progress_threshold">412dp</dimen>
     <!-- The minimum display position of the arrow on the screen -->
     <dimen name="navigation_edge_arrow_min_y">64dp</dimen>
     <!-- The amount by which the arrow is shifted to avoid the finger-->
     <dimen name="navigation_edge_finger_offset">64dp</dimen>
+    <!-- The threshold to dynamically activate the edge action -->
+    <dimen name="navigation_edge_action_reactivation_drag_threshold">32dp</dimen>
+    <!-- The threshold to dynamically deactivate the edge action -->
+    <dimen name="navigation_edge_action_deactivation_drag_threshold">32dp</dimen>
 
     <!-- The thickness of the arrow -->
     <dimen name="navigation_edge_arrow_thickness">4dp</dimen>
@@ -56,37 +62,61 @@
     <dimen name="navigation_edge_minimum_x_delta_for_switch">32dp</dimen>
 
     <!-- entry state -->
+    <item name="navigation_edge_entry_scale" format="float" type="dimen">0.98</item>
     <dimen name="navigation_edge_entry_margin">4dp</dimen>
-    <dimen name="navigation_edge_entry_background_width">8dp</dimen>
-    <dimen name="navigation_edge_entry_background_height">60dp</dimen>
-    <dimen name="navigation_edge_entry_edge_corners">30dp</dimen>
-    <dimen name="navigation_edge_entry_far_corners">30dp</dimen>
-    <dimen name="navigation_edge_entry_arrow_length">10dp</dimen>
-    <dimen name="navigation_edge_entry_arrow_height">7dp</dimen>
+    <item name="navigation_edge_entry_background_alpha" format="float" type="dimen">1.0</item>
+    <dimen name="navigation_edge_entry_background_width">0dp</dimen>
+    <dimen name="navigation_edge_entry_background_height">48dp</dimen>
+    <dimen name="navigation_edge_entry_edge_corners">6dp</dimen>
+    <dimen name="navigation_edge_entry_far_corners">6dp</dimen>
+    <item name="navigation_edge_entry_arrow_alpha" format="float" type="dimen">0.0</item>
+    <dimen name="navigation_edge_entry_arrow_length">8.6dp</dimen>
+    <dimen name="navigation_edge_entry_arrow_height">5dp</dimen>
 
     <!-- pre-threshold -->
     <dimen name="navigation_edge_pre_threshold_margin">4dp</dimen>
-    <dimen name="navigation_edge_pre_threshold_background_width">64dp</dimen>
-    <dimen name="navigation_edge_pre_threshold_background_height">60dp</dimen>
-    <dimen name="navigation_edge_pre_threshold_edge_corners">22dp</dimen>
-    <dimen name="navigation_edge_pre_threshold_far_corners">26dp</dimen>
+    <item name="navigation_edge_pre_threshold_background_alpha" format="float" type="dimen">1.0
+    </item>
+    <item name="navigation_edge_pre_threshold_scale" format="float" type="dimen">0.98</item>
+    <dimen name="navigation_edge_pre_threshold_background_width">51dp</dimen>
+    <dimen name="navigation_edge_pre_threshold_background_height">46dp</dimen>
+    <dimen name="navigation_edge_pre_threshold_edge_corners">16dp</dimen>
+    <dimen name="navigation_edge_pre_threshold_far_corners">20dp</dimen>
+    <item name="navigation_edge_pre_threshold_arrow_alpha" format="float" type="dimen">1.0</item>
+    <dimen name="navigation_edge_pre_threshold_arrow_length">8dp</dimen>
+    <dimen name="navigation_edge_pre_threshold_arrow_height">5.6dp</dimen>
 
-    <!-- post-threshold / active -->
+    <!-- active (post-threshold) -->
+    <item name="navigation_edge_active_scale" format="float" type="dimen">1.0</item>
     <dimen name="navigation_edge_active_margin">14dp</dimen>
-    <dimen name="navigation_edge_active_background_width">60dp</dimen>
-    <dimen name="navigation_edge_active_background_height">60dp</dimen>
-    <dimen name="navigation_edge_active_edge_corners">30dp</dimen>
-    <dimen name="navigation_edge_active_far_corners">30dp</dimen>
-    <dimen name="navigation_edge_active_arrow_length">8dp</dimen>
-    <dimen name="navigation_edge_active_arrow_height">9dp</dimen>
+    <item name="navigation_edge_active_background_alpha" format="float" type="dimen">1.0</item>
+    <dimen name="navigation_edge_active_background_width">48dp</dimen>
+    <dimen name="navigation_edge_active_background_height">48dp</dimen>
+    <dimen name="navigation_edge_active_edge_corners">24dp</dimen>
+    <dimen name="navigation_edge_active_far_corners">24dp</dimen>
+    <item name="navigation_edge_active_arrow_alpha" format="float" type="dimen">1.0</item>
+    <dimen name="navigation_edge_active_arrow_length">6.4dp</dimen>
+    <dimen name="navigation_edge_active_arrow_height">7.2dp</dimen>
 
+    <!-- committed -->
+    <item name="navigation_edge_committed_scale" format="float" type="dimen">0.85</item>
+    <item name="navigation_edge_committed_alpha" format="float" type="dimen">0</item>
+
+    <!-- cancelled -->
+    <dimen name="navigation_edge_cancelled_background_width">0dp</dimen>
+
+    <item name="navigation_edge_stretch_scale" format="float" type="dimen">1.0</item>
     <dimen name="navigation_edge_stretch_margin">18dp</dimen>
-    <dimen name="navigation_edge_stretch_background_width">74dp</dimen>
-    <dimen name="navigation_edge_stretch_background_height">60dp</dimen>
-    <dimen name="navigation_edge_stretch_edge_corners">30dp</dimen>
-    <dimen name="navigation_edge_stretch_far_corners">30dp</dimen>
-    <dimen name="navigation_edge_stretched_arrow_length">7dp</dimen>
-    <dimen name="navigation_edge_stretched_arrow_height">10dp</dimen>
+    <dimen name="navigation_edge_stretch_background_width">60dp</dimen>
+    <item name="navigation_edge_stretch_background_alpha" format="float" type="dimen">
+        @dimen/navigation_edge_entry_background_alpha
+    </item>
+    <dimen name="navigation_edge_stretch_background_height">48dp</dimen>
+    <dimen name="navigation_edge_stretch_edge_corners">24dp</dimen>
+    <dimen name="navigation_edge_stretch_far_corners">24dp</dimen>
+    <item name="navigation_edge_strech_arrow_alpha" format="float" type="dimen">1.0</item>
+    <dimen name="navigation_edge_stretched_arrow_length">5.6dp</dimen>
+    <dimen name="navigation_edge_stretched_arrow_height">8dp</dimen>
 
     <dimen name="navigation_edge_cancelled_arrow_length">12dp</dimen>
     <dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
@@ -337,7 +367,7 @@
     <!-- Used for both start and bottom margin of the preview, relative to the action container -->
     <dimen name="overlay_preview_container_margin">8dp</dimen>
     <dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
-    <dimen name="overlay_action_container_margin_bottom">4dp</dimen>
+    <dimen name="overlay_action_container_margin_bottom">6dp</dimen>
     <dimen name="overlay_bg_protection_height">242dp</dimen>
     <dimen name="overlay_action_container_corner_radius">18dp</dimen>
     <dimen name="overlay_action_container_padding_vertical">4dp</dimen>
@@ -762,12 +792,10 @@
     <dimen name="go_to_full_shade_appearing_translation">200dp</dimen>
 
     <!-- The width/height of the keyguard bottom area icon view on keyguard. -->
-    <dimen name="keyguard_affordance_height">48dp</dimen>
-    <dimen name="keyguard_affordance_width">48dp</dimen>
-
     <dimen name="keyguard_affordance_fixed_height">48dp</dimen>
     <dimen name="keyguard_affordance_fixed_width">48dp</dimen>
     <dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
+
     <!-- Amount the button should shake when it's not long-pressed for long enough. -->
     <dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
 
@@ -1070,8 +1098,13 @@
     <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
     <dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
     <dimen name="qs_media_rec_album_size">88dp</dimen>
+    <dimen name="qs_media_rec_album_width">110dp</dimen>
+    <dimen name="qs_media_rec_album_height_expanded">108dp</dimen>
+    <dimen name="qs_media_rec_album_height_collapsed">77dp</dimen>
     <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
     <dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
+    <dimen name="qs_media_rec_album_title_bottom_margin">22dp</dimen>
+    <dimen name="qs_media_rec_album_subtitle_height">12dp</dimen>
 
     <!-- Media tap-to-transfer chip for sender device -->
     <dimen name="media_ttt_chip_outer_padding">16dp</dimen>
@@ -1089,6 +1122,7 @@
          (112 - 40) / 2 = 36dp -->
     <dimen name="media_ttt_generic_icon_padding">36dp</dimen>
     <dimen name="media_ttt_receiver_vert_translation">40dp</dimen>
+    <dimen name="media_ttt_receiver_icon_bottom_margin">10dp</dimen>
 
     <!-- Window magnification -->
     <dimen name="magnification_border_drag_size">35dp</dimen>
@@ -1111,7 +1145,7 @@
 
     <!-- Home Controls -->
     <dimen name="controls_header_menu_size">48dp</dimen>
-    <dimen name="controls_header_bottom_margin">24dp</dimen>
+    <dimen name="controls_header_bottom_margin">16dp</dimen>
     <dimen name="controls_header_app_icon_size">24dp</dimen>
     <dimen name="controls_top_margin">48dp</dimen>
     <dimen name="controls_padding_horizontal">0dp</dimen>
@@ -1649,4 +1683,10 @@
     <dimen name="rear_display_animation_height">200dp</dimen>
     <dimen name="rear_display_title_top_padding">24dp</dimen>
     <dimen name="rear_display_title_bottom_padding">16dp</dimen>
+
+    <!--
+    Vertical distance between the pointer and the popup menu that shows up on the lock screen when
+    it is long-pressed.
+    -->
+    <dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index c5ffc94..6354752 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -33,8 +33,6 @@
     <!-- Whether to show chipbar UI whenever the device is unlocked by ActiveUnlock. -->
     <bool name="flag_active_unlock_chipbar">true</bool>
 
-    <bool name="flag_smartspace">false</bool>
-
     <!--  Whether the user switcher chip shows in the status bar. When true, the multi user
       avatar will no longer show on the lockscreen -->
     <bool name="flag_user_switcher_chip">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2de16a4..227b00e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -240,9 +240,13 @@
     <!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
     <string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
     <!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
-    <string name="screenshot_work_profile_notification">Work screenshots are saved in the <xliff:g id="app" example="Work Files">%1$s</xliff:g> app</string>
+    <string name="screenshot_work_profile_notification">Saved in <xliff:g id="app" example="Files">%1$s</xliff:g> in the work profile</string>
     <!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
     <string name="screenshot_default_files_app_name">Files</string>
+    <!-- A notice shown to the user to indicate that an app has detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
+    <string name="screenshot_detected_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> detected this screenshot.</string>
+    <!-- A notice shown to the user to indicate that multiple apps have detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
+    <string name="screenshot_detected_multiple_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> and other open apps detected this screenshot.</string>
 
     <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
     <string name="screenrecord_name">Screen Recorder</string>
@@ -1455,10 +1459,10 @@
     <string name="notification_conversation_summary_low">No sound or vibration and appears lower in conversation section</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
-    <string name="notification_channel_summary_default">May ring or vibrate based on phone settings</string>
+    <string name="notification_channel_summary_default">May ring or vibrate based on device settings</string>
 
     <!-- [CHAR LIMIT=150] Conversation Notification Importance title: normal conversation level, with bubbling summary -->
-    <string name="notification_channel_summary_default_with_bubbles">May ring or vibrate based on phone settings. Conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default.</string>
+    <string name="notification_channel_summary_default_with_bubbles">May ring or vibrate based on device settings. Conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default.</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: automatic importance level summary -->
     <string name="notification_channel_summary_automatic">Have the system determine if this notification should make sound or vibration</string>
@@ -2238,6 +2242,14 @@
     <!-- Removed control in management screen [CHAR LIMIT=20] -->
     <string name="controls_removed">Removed</string>
 
+    <!-- Title for the dialog presented to the user to authorize this app to display a Device
+         controls panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=30] -->
+    <string name="controls_panel_authorization_title">Add <xliff:g id="appName" example="My app">%s</xliff:g>?</string>
+
+    <!-- Shows in a dialog presented to the user to authorize this app to display a Device controls
+         panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
+    <string name="controls_panel_authorization">When you add <xliff:g id="appName" example="My app">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here.</string>
+
     <!-- a11y state description for a control that is currently favorited [CHAR LIMIT=NONE] -->
     <string name="accessibility_control_favorite">Favorited</string>
     <!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] -->
@@ -2352,6 +2364,8 @@
     <string name="controls_media_smartspace_rec_item_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
     <!-- Description for Smartspace recommendation's media item which doesn't have artist info, including information for the media's title and the source app [CHAR LIMIT=NONE]-->
     <string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string>
+    <!-- Header title for Smartspace recommendation card within media controls. [CHAR_LIMIT=30] -->
+    <string name="controls_media_smartspace_rec_header">For You</string>
 
     <!--- ****** Media tap-to-transfer ****** -->
     <!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->
@@ -2387,6 +2401,8 @@
     <string name="controls_menu_add">Add controls</string>
     <!-- Controls menu, edit [CHAR_LIMIT=30] -->
     <string name="controls_menu_edit">Edit controls</string>
+    <!-- Controls menu, add another app [CHAR LIMIT=30] -->
+    <string name="controls_menu_add_another_app">Add app</string>
 
     <!-- Title for the media output dialog with media related devices [CHAR LIMIT=50] -->
     <string name="media_output_dialog_add_output">Add outputs</string>
@@ -2418,6 +2434,8 @@
     <string name="media_output_group_title_speakers_and_displays">Speakers &amp; Displays</string>
     <!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] -->
     <string name="media_output_group_title_suggested_device">Suggested Devices</string>
+    <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] -->
+    <string name="media_output_status_require_premium">Requires premium account</string>
 
     <!-- Media Output Broadcast Dialog -->
     <!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
@@ -2777,4 +2795,34 @@
     <string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string>
     <!-- Subtitle for the notification sent when a stylus battery is low. [CHAR LIMIT=none]-->
     <string name="stylus_battery_low_subtitle">Connect your stylus to a charger</string>
+
+    <!-- Title for notification of low stylus battery. [CHAR_LIMIT=NONE] -->
+    <string name="stylus_battery_low">Stylus battery low</string>
+
+    <!-- Label for a lock screen shortcut to start the camera in video mode. [CHAR_LIMIT=16] -->
+    <string name="video_camera">Video camera</string>
+
+    <!-- Switch to work profile dialer app for placing a call dialog. -->
+    <!-- Text for Switch to work profile dialog's Title. Switch to work profile dialog guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=60] -->
+    <string name="call_from_work_profile_title">Can\'t call from this profile</string>
+    <!-- Text for switch to work profile for call dialog to guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=NONE]
+    -->
+    <string name="call_from_work_profile_text">Your work policy allows you to make phone calls only from the work profile</string>
+    <!-- Label for the button to switch to work profile for placing call. Switch to work profile dialog guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
+    <string name="call_from_work_profile_action">Switch to work profile</string>
+    <!-- Label for the close button on switch to work profile dialog. Switch to work profile dialog guide users to make call from work
+    profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
+    <string name="call_from_work_profile_close">Close</string>
+
+    <!--
+    Label for a menu item in a menu that is shown when the user wishes to configure the lock screen.
+    Clicking on this menu item takes the user to a screen where they can modify the settings of the
+    lock screen.
+
+    [CHAR LIMIT=32]
+    -->
+    <string name="lock_screen_settings">Lock screen settings</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9846fc2..58b0234 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -678,6 +678,17 @@
 
     <style name="MediaPlayer.Recommendation"/>
 
+    <style name="MediaPlayer.Recommendation.Header">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginTop">@dimen/qs_media_padding</item>
+        <item name="android:layout_marginStart">@dimen/qs_media_padding</item>
+        <item name="android:fontFamily">=@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
     <style name="MediaPlayer.Recommendation.AlbumContainer">
         <item name="android:layout_width">@dimen/qs_media_rec_album_size</item>
         <item name="android:layout_height">@dimen/qs_media_rec_album_size</item>
@@ -686,6 +697,12 @@
         <item name="android:layout_marginBottom">@dimen/qs_media_rec_album_bottom_margin</item>
     </style>
 
+    <style name="MediaPlayer.Recommendation.AlbumContainer.Updated">
+        <item name="android:layout_width">@dimen/qs_media_rec_album_width</item>
+        <item name="android:background">@drawable/qs_media_light_source</item>
+        <item name="android:layout_marginTop">@dimen/qs_media_info_spacing</item>
+    </style>
+
     <style name="MediaPlayer.Recommendation.Album">
         <item name="android:backgroundTint">@color/media_player_album_bg</item>
     </style>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 06d425c..bf576dc 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,105 +14,73 @@
   ~ limitations under the License.
   -->
 
-<ConstraintSet
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/large_screen_header_constraint">
 
-    <Constraint
-        android:id="@+id/clock">
+    <Constraint android:id="@+id/clock">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/date"
-            app:layout_constraintHorizontal_bias="0"
-        />
-        <Transform
-            android:scaleX="1"
-            android:scaleY="1"
-            />
+            app:layout_constraintStart_toEndOf="@id/begin_guide"
+            app:layout_constraintTop_toTopOf="parent" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/date">
+    <Constraint android:id="@+id/date">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
+            android:layout_marginStart="8dp"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toEndOf="@id/clock"
-            app:layout_constraintEnd_toStartOf="@id/carrier_group"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="0"
-        />
+            app:layout_constraintTop_toTopOf="parent" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/carrier_group">
+    <Constraint android:id="@+id/carrier_group">
         <Layout
-            app:layout_constraintWidth_min="48dp"
             android:layout_width="0dp"
             android:layout_height="0dp"
-            app:layout_constrainedWidth="true"
             android:layout_gravity="end|center_vertical"
-            android:layout_marginStart="8dp"
-            app:layout_constraintStart_toEndOf="@id/date"
-            app:layout_constraintEnd_toStartOf="@id/statusIcons"
-            app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            />
-        <PropertySet
-            android:alpha="1"
-        />
+            app:layout_constraintEnd_toStartOf="@id/statusIcons"
+            app:layout_constraintStart_toEndOf="@id/date"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintWidth_default="wrap"
+            app:layout_constraintWidth_min="48dp" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/statusIcons">
+    <Constraint android:id="@+id/statusIcons">
         <Layout
-            app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
             android:layout_width="wrap_content"
             android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toEndOf="@id/carrier_group"
-            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
-            app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintHorizontal_bias="1"
-            />
-        <PropertySet
-            android:alpha="1"
-        />
+            app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintEnd_toEndOf="@id/carrier_group"/>
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/batteryRemainingIcon">
+    <Constraint android:id="@+id/batteryRemainingIcon">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="0dp"
             app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toEndOf="@id/statusIcons"
-            app:layout_constraintEnd_toStartOf="@id/privacy_container"
-            app:layout_constraintTop_toTopOf="@id/clock"
             app:layout_constraintBottom_toBottomOf="parent"
-        />
-        <PropertySet
-            android:alpha="1"
-        />
+            app:layout_constraintEnd_toStartOf="@id/privacy_container"
+            app:layout_constraintTop_toTopOf="parent" />
+        <PropertySet android:alpha="1" />
     </Constraint>
 
-    <Constraint
-        android:id="@+id/privacy_container">
+    <Constraint android:id="@+id/privacy_container">
         <Layout
             android:layout_width="wrap_content"
             android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/date"
-            app:layout_constraintBottom_toBottomOf="@id/date"
-            app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon"
-            app:layout_constraintHorizontal_bias="1"
-        />
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/end_guide"
+            app:layout_constraintTop_toTopOf="parent" />
     </Constraint>
-
 </ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
new file mode 100644
index 0000000..d3be3c7
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<ConstraintSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+
+    <Constraint
+        android:id="@+id/sizing_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_collapsed"
+        />
+
+    <Constraint
+        android:id="@+id/media_rec_title"
+        style="@style/MediaPlayer.Recommendation.Header"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"/>
+
+    <Constraint
+        android:id="@+id/media_cover1_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        android:layout_marginStart="@dimen/qs_media_padding"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
+
+
+    <Constraint
+        android:id="@+id/media_cover2_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
+
+    <Constraint
+        android:id="@+id/media_cover3_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_collapsed"
+        android:layout_marginEnd="@dimen/qs_media_padding"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+
+</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
new file mode 100644
index 0000000..88c7055
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<ConstraintSet
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+
+    <Constraint
+        android:id="@+id/sizing_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_expanded"
+        />
+
+    <Constraint
+        android:id="@+id/media_rec_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginStart="@dimen/qs_media_padding"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:singleLine="true"
+        android:textSize="14sp"
+        android:textColor="@color/notification_primary_text_color"/>
+
+    <Constraint
+        android:id="@+id/media_cover1_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        android:layout_marginStart="@dimen/qs_media_padding"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/media_cover2_container"/>
+
+
+    <Constraint
+        android:id="@+id/media_cover2_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+        android:layout_marginEnd="@dimen/qs_media_info_spacing"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toEndOf="@id/media_cover1_container"
+        app:layout_constraintEnd_toStartOf="@id/media_cover3_container"/>
+
+    <Constraint
+        android:id="@+id/media_cover3_container"
+        style="@style/MediaPlayer.Recommendation.AlbumContainer.Updated"
+        android:layout_height="@dimen/qs_media_rec_album_height_expanded"
+        android:layout_marginEnd="@dimen/qs_media_padding"
+        app:layout_constraintTop_toBottomOf="@+id/media_rec_title"
+        app:layout_constraintStart_toEndOf="@id/media_cover2_container"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+
+</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index e56e5d5..00a0444 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -48,8 +48,7 @@
             app:layout_constrainedWidth="true"
             app:layout_constraintStart_toEndOf="@id/clock"
             app:layout_constraintEnd_toStartOf="@id/barrier"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintBaseline_toBaselineOf="@id/clock"
             app:layout_constraintHorizontal_bias="0"
         />
     </Constraint>
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index d97031f..52a98984 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -41,9 +41,6 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/privacy_container"
             app:layout_constraintBottom_toBottomOf="@id/carrier_group"
-            app:layout_constraintEnd_toStartOf="@id/carrier_group"
-            app:layout_constraintHorizontal_bias="0"
-            app:layout_constraintHorizontal_chainStyle="spread_inside"
         />
         <Transform
             android:scaleX="2.57"
@@ -62,18 +59,18 @@
         />
     </Constraint>
 
+    <!-- LargeScreenShadeHeaderController helps with managing clock width to layout this view -->
     <Constraint
         android:id="@+id/carrier_group">
         <Layout
-            app:layout_constraintWidth_min="48dp"
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
             android:layout_height="@dimen/large_screen_shade_header_min_height"
-            app:layout_constraintStart_toEndOf="@id/clock"
+            app:layout_constraintWidth_min="48dp"
+            app:layout_constraintWidth_default="wrap"
+            app:layout_constraintStart_toStartOf="@id/clock"
             app:layout_constraintTop_toBottomOf="@id/privacy_container"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintHorizontal_bias="1"
             app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
-            app:layout_constraintHorizontal_chainStyle="spread_inside"
             />
         <PropertySet
             android:alpha="1"
diff --git a/packages/SystemUI/scripts/token_alignment/.eslintrc.json b/packages/SystemUI/scripts/token_alignment/.eslintrc.json
new file mode 100644
index 0000000..69dc00e
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/.eslintrc.json
@@ -0,0 +1,31 @@
+{
+    "env": {
+        "es2021": true,
+        "node": true
+    },
+    "parserOptions": {
+        "ecmaVersion": "latest",
+        "sourceType": "module"
+    },
+    "plugins": ["prettier", "@typescript-eslint", "eslint-plugin-simple-import-sort", "import"],
+    "extends": ["prettier", "eslint:recommended", "plugin:@typescript-eslint/recommended"],
+    "rules": {
+        "prettier/prettier": ["error"],
+        "no-unused-vars": "off",
+        "@typescript-eslint/no-unused-vars": [
+            "warn",
+            {
+                "argsIgnorePattern": "^_",
+                "varsIgnorePattern": "^_",
+                "caughtErrorsIgnorePattern": "^_"
+            }
+        ],
+        "no-multiple-empty-lines": ["error", { "max": 2 }],
+        "no-multi-spaces": "error",
+        "simple-import-sort/imports": "error",
+        "simple-import-sort/exports": "error",
+        "import/first": "error",
+        "import/newline-after-import": "error",
+        "import/no-duplicates": "error"
+    }
+}
diff --git a/packages/SystemUI/scripts/token_alignment/.gitignore b/packages/SystemUI/scripts/token_alignment/.gitignore
new file mode 100644
index 0000000..96ce14f
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/.gitignore
@@ -0,0 +1,2 @@
+vscode
+node_modules
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/.prettierrc b/packages/SystemUI/scripts/token_alignment/.prettierrc
new file mode 100644
index 0000000..20f02f9
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/.prettierrc
@@ -0,0 +1,9 @@
+{
+   "tabWidth": 4,
+   "printWidth": 100,
+   "semi": true,
+   "singleQuote": true,
+    "bracketSameLine": true,
+    "bracketSpacing": true,
+    "arrowParens": "always"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts b/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts
new file mode 100644
index 0000000..80e075c
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/DOMFuncs.ts
@@ -0,0 +1,297 @@
+// Copyright 2022 Google LLC
+
+// 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.
+
+type IElementComment =
+    | { commentNode: undefined; textContent: undefined; hidden: undefined }
+    | { commentNode: Node; textContent: string; hidden: boolean };
+
+interface ITag {
+    attrs?: Record<string, string | number>;
+    tagName: string;
+}
+
+export interface INewTag extends ITag {
+    content?: string | number;
+    comment?: string;
+}
+
+export type IUpdateTag = Partial<Omit<INewTag, 'tagName'>>;
+
+export default class DOM {
+    static addEntry(containerElement: Element, tagOptions: INewTag) {
+        const doc = containerElement.ownerDocument;
+        const exists = this.alreadyHasEntry(containerElement, tagOptions);
+
+        if (exists) {
+            console.log('Ignored adding entry already available: ', exists.outerHTML);
+            return;
+        }
+
+        let insertPoint: Node | null = containerElement.lastElementChild; //.childNodes[containerElement.childNodes.length - 1];
+
+        if (!insertPoint) {
+            console.log('Ignored adding entry in empity parent: ', containerElement.outerHTML);
+            return;
+        }
+
+        const { attrs, comment, content, tagName } = tagOptions;
+
+        if (comment) {
+            const commentNode = doc.createComment(comment);
+            this.insertAfterIdented(commentNode, insertPoint);
+            insertPoint = commentNode;
+        }
+
+        const newEl = doc.createElement(tagName);
+        if (content) newEl.innerHTML = content.toString();
+        if (attrs)
+            Object.entries(attrs).forEach(([attr, value]) =>
+                newEl.setAttribute(attr, value.toString())
+            );
+        this.insertAfterIdented(newEl, insertPoint);
+
+        return true;
+    }
+
+    static insertBeforeIndented(newNode: Node, referenceNode: Node) {
+        const paddingNode = referenceNode.previousSibling;
+        const ownerDoc = referenceNode.ownerDocument;
+        const containerNode = referenceNode.parentNode;
+
+        if (!paddingNode || !ownerDoc || !containerNode) return;
+
+        const currentPadding = paddingNode.textContent || '';
+        const textNode = referenceNode.ownerDocument.createTextNode(currentPadding);
+
+        containerNode.insertBefore(newNode, referenceNode);
+        containerNode.insertBefore(textNode, newNode);
+    }
+
+    static insertAfterIdented(newNode: Node, referenceNode: Node) {
+        const paddingNode = referenceNode.previousSibling;
+        const ownerDoc = referenceNode.ownerDocument;
+        const containerNode = referenceNode.parentNode;
+
+        if (!paddingNode || !ownerDoc || !containerNode) return;
+
+        const currentPadding = paddingNode.textContent || '';
+        const textNode = ownerDoc.createTextNode(currentPadding);
+
+        containerNode.insertBefore(newNode, referenceNode.nextSibling);
+        containerNode.insertBefore(textNode, newNode);
+    }
+
+    static getElementComment(el: Element): IElementComment {
+        const commentNode = el.previousSibling?.previousSibling;
+
+        const out = { commentNode: undefined, textContent: undefined, hidden: undefined };
+
+        if (!commentNode) return out;
+
+        const textContent = commentNode.textContent || '';
+        const hidden = textContent.substring(textContent.length - 6) == '@hide ';
+
+        if (!(commentNode && commentNode.nodeName == '#comment')) return out;
+
+        return { commentNode, textContent, hidden: hidden };
+    }
+
+    static duplicateEntryWithChange(
+        templateElement: Element,
+        options: Omit<IUpdateTag, 'content'>
+    ) {
+        const exists = this.futureEntryAlreadyExist(templateElement, options);
+        if (exists) {
+            console.log('Ignored duplicating entry already available: ', exists.outerHTML);
+            return;
+        }
+
+        const { commentNode } = this.getElementComment(templateElement);
+        let insertPoint: Node = templateElement;
+
+        if (commentNode) {
+            const newComment = commentNode.cloneNode();
+            this.insertAfterIdented(newComment, insertPoint);
+            insertPoint = newComment;
+        }
+
+        const newEl = templateElement.cloneNode(true) as Element;
+        this.insertAfterIdented(newEl, insertPoint);
+
+        this.updateElement(newEl, options);
+        return true;
+    }
+
+    static replaceStringInAttributeValueOnQueried(
+        root: Element,
+        query: string,
+        attrArray: string[],
+        replaceMap: Map<string, string>
+    ): boolean {
+        let updated = false;
+        const queried = [...Array.from(root.querySelectorAll(query)), root];
+
+        queried.forEach((el) => {
+            attrArray.forEach((attr) => {
+                if (el.hasAttribute(attr)) {
+                    const currentAttrValue = el.getAttribute(attr);
+
+                    if (!currentAttrValue) return;
+
+                    [...replaceMap.entries()].some(([oldStr, newStr]) => {
+                        if (
+                            currentAttrValue.length >= oldStr.length &&
+                            currentAttrValue.indexOf(oldStr) ==
+                                currentAttrValue.length - oldStr.length
+                        ) {
+                            el.setAttribute(attr, currentAttrValue.replace(oldStr, newStr));
+                            updated = true;
+                            return true;
+                        }
+                        return false;
+                    });
+                }
+            });
+        });
+
+        return updated;
+    }
+
+    static updateElement(el: Element, updateOptions: IUpdateTag) {
+        const exists = this.futureEntryAlreadyExist(el, updateOptions);
+        if (exists) {
+            console.log('Ignored updating entry already available: ', exists.outerHTML);
+            return;
+        }
+
+        const { comment, attrs, content } = updateOptions;
+
+        if (comment) {
+            const { commentNode } = this.getElementComment(el);
+            if (commentNode) {
+                commentNode.textContent = comment;
+            }
+        }
+
+        if (attrs) {
+            for (const attr in attrs) {
+                const value = attrs[attr];
+
+                if (value != undefined) {
+                    el.setAttribute(attr, `${value}`);
+                } else {
+                    el.removeAttribute(attr);
+                }
+            }
+        }
+
+        if (content != undefined) {
+            el.innerHTML = `${content}`;
+        }
+
+        return true;
+    }
+
+    static elementToOptions(el: Element): ITag {
+        return {
+            attrs: this.getAllElementAttributes(el),
+            tagName: el.tagName,
+        };
+    }
+
+    static getAllElementAttributes(el: Element): Record<string, string> {
+        return el
+            .getAttributeNames()
+            .reduce(
+                (acc, attr) => ({ ...acc, [attr]: el.getAttribute(attr) || '' }),
+                {} as Record<string, string>
+            );
+    }
+
+    static futureEntryAlreadyExist(el: Element, updateOptions: IUpdateTag) {
+        const currentElOptions = this.elementToOptions(el);
+
+        if (!el.parentElement) {
+            console.log('Checked el has no parent');
+            process.exit();
+        }
+
+        return this.alreadyHasEntry(el.parentElement, {
+            ...currentElOptions,
+            ...updateOptions,
+            attrs: { ...currentElOptions.attrs, ...updateOptions.attrs },
+        });
+    }
+
+    static alreadyHasEntry(
+        containerElement: Element,
+        { attrs, tagName }: Pick<INewTag, 'attrs' | 'tagName'>
+    ) {
+        const qAttrs = attrs
+            ? Object.entries(attrs)
+                  .map(([a, v]) => `[${a}="${v}"]`)
+                  .join('')
+            : '';
+
+        return containerElement.querySelector(tagName + qAttrs);
+    }
+
+    static replaceContentTextOnQueried(
+        root: Element,
+        query: string,
+        replacePairs: Array<[string, string]>
+    ) {
+        let updated = false;
+        let queried = Array.from(root.querySelectorAll(query));
+
+        if (queried.length == 0) queried = [...Array.from(root.querySelectorAll(query)), root];
+
+        queried.forEach((el) => {
+            replacePairs.forEach(([oldStr, newStr]) => {
+                if (el.innerHTML == oldStr) {
+                    el.innerHTML = newStr;
+                    updated = true;
+                }
+            });
+        });
+
+        return updated;
+    }
+
+    static XMLDocToString(doc: XMLDocument) {
+        let str = '';
+
+        doc.childNodes.forEach((node) => {
+            switch (node.nodeType) {
+                case 8: // comment
+                    str += `<!--${node.nodeValue}-->\n`;
+                    break;
+
+                case 3: // text
+                    str += node.textContent;
+                    break;
+
+                case 1: // element
+                    str += (node as Element).outerHTML;
+                    break;
+
+                default:
+                    console.log('Unhandled node type: ' + node.nodeType);
+                    break;
+            }
+        });
+
+        return str;
+    }
+}
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/FileIO.ts b/packages/SystemUI/scripts/token_alignment/helpers/FileIO.ts
new file mode 100644
index 0000000..359e3ab
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/FileIO.ts
@@ -0,0 +1,112 @@
+// Copyright 2022 Google LLC
+
+// 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.import { exec } from 'child_process';
+
+import { exec } from 'child_process';
+import { parse } from 'csv-parse';
+import { promises as fs } from 'fs';
+import jsdom from 'jsdom';
+
+const DOMParser = new jsdom.JSDOM('').window.DOMParser as typeof window.DOMParser;
+
+type TFileList = string[];
+
+export type TCSVRecord = Array<string | boolean | number>;
+
+class _FileIO {
+    public parser = new DOMParser();
+    public saved: string[] = [];
+
+    public loadXML = async (path: string): Promise<XMLDocument> => {
+        try {
+            const src = await this.loadFileAsText(path);
+            return this.parser.parseFromString(src, 'text/xml') as XMLDocument;
+        } catch (error) {
+            console.log(`Failed to parse XML file '${path}'.`, error);
+            process.exit();
+        }
+    };
+
+    public loadFileAsText = async (path: string): Promise<string> => {
+        try {
+            return await fs.readFile(path, { encoding: 'utf8' });
+        } catch (error) {
+            console.log(`Failed to read file '${path}'.`, error);
+            process.exit();
+        }
+    };
+
+    public saveFile = async (data: string, path: string) => {
+        try {
+            await fs.writeFile(path, data, { encoding: 'utf8' });
+            this.saved.push(path);
+        } catch (error) {
+            console.log(error);
+            console.log(`Failed to write file '${path}'.`);
+            process.exit();
+        }
+    };
+
+    public loadFileList = async (path: string): Promise<TFileList> => {
+        const src = await this.loadFileAsText(path);
+
+        try {
+            return JSON.parse(src) as TFileList;
+        } catch (error) {
+            console.log(error);
+            console.log(`Failed to parse JSON file '${path}'.`);
+            process.exit();
+        }
+    };
+
+    public loadCSV = (path: string): Promise<Array<TCSVRecord>> => {
+        return new Promise((resolve, reject) => {
+            this.loadFileAsText(path).then((src) => {
+                parse(
+                    src,
+                    {
+                        delimiter: '	',
+                    },
+                    (err, records) => {
+                        if (err) {
+                            reject(err);
+                            return;
+                        }
+
+                        resolve(records);
+                    }
+                );
+            });
+        });
+    };
+
+    formatSaved = () => {
+        const cmd = `idea format ${this.saved.join(' ')}`;
+
+        exec(cmd, (error, out, stderr) => {
+            if (error) {
+                console.log(error.message);
+                return;
+            }
+
+            if (stderr) {
+                console.log(stderr);
+                return;
+            }
+
+            console.log(out);
+        });
+    };
+}
+
+export const FileIO = new _FileIO();
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/migrationList.ts b/packages/SystemUI/scripts/token_alignment/helpers/migrationList.ts
new file mode 100644
index 0000000..8d50644
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/migrationList.ts
@@ -0,0 +1,70 @@
+// Copyright 2022 Google LLC
+
+// 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.import { exec } from 'child_process';
+
+import { FileIO, TCSVRecord } from './FileIO';
+import ProcessArgs from './processArgs';
+
+interface IInputMigItem {
+    migrationToken: string;
+    materialToken: string;
+    newDefaultValue?: string;
+    newComment?: string;
+}
+
+interface IAditionalKeys {
+    step: ('update' | 'duplicate' | 'add' | 'ignore')[];
+    isHidden: boolean;
+    replaceToken: string;
+}
+
+export type IMigItem = Omit<IInputMigItem, 'materialToken' | 'migrationToken'> & IAditionalKeys;
+
+export type IMigrationMap = Map<string, IMigItem>;
+
+function isMigrationRecord(record: TCSVRecord): record is string[] {
+    return !record.some((value) => typeof value != 'string') || record.length != 5;
+}
+
+export const loadMIgrationList = async function (): Promise<IMigrationMap> {
+    const out: IMigrationMap = new Map();
+    const csv = await FileIO.loadCSV('resources/migrationList.csv');
+
+    csv.forEach((record, i) => {
+        if (i == 0) return; // header
+
+        if (typeof record[0] != 'string') return;
+
+        if (!isMigrationRecord(record)) {
+            console.log(`Failed to validade CSV record as string[5].`, record);
+            process.exit();
+        }
+
+        const [originalToken, materialToken, newDefaultValue, newComment, migrationToken] = record;
+
+        if (out.has(originalToken)) {
+            console.log('Duplicated entry on Migration CSV file: ', originalToken);
+            return;
+        }
+
+        out.set(originalToken, {
+            replaceToken: ProcessArgs.isDebug ? migrationToken : materialToken,
+            ...(!!newDefaultValue && { newDefaultValue }),
+            ...(!!newComment && { newComment }),
+            step: [],
+            isHidden: false,
+        });
+    });
+
+    return new Map([...out].sort((a, b) => b[0].length - a[0].length));
+};
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/processArgs.ts b/packages/SystemUI/scripts/token_alignment/helpers/processArgs.ts
new file mode 100644
index 0000000..be0e232
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/processArgs.ts
@@ -0,0 +1,21 @@
+// Copyright 2022 Google LLC
+
+// 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.import { exec } from 'child_process';
+
+const myArgs = process.argv.slice(2);
+
+const ProcessArgs = {
+    isDebug: myArgs.includes('debug'),
+};
+
+export default ProcessArgs;
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/processXML.ts b/packages/SystemUI/scripts/token_alignment/helpers/processXML.ts
new file mode 100644
index 0000000..368d4cb
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/processXML.ts
@@ -0,0 +1,102 @@
+// Copyright 2022 Google LLC
+
+// 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.import { exec } from 'child_process';
+
+import DOM, { INewTag, IUpdateTag } from './DOMFuncs';
+import { FileIO } from './FileIO';
+import { IMigItem, IMigrationMap } from './migrationList';
+
+export type TResultExistingEval = ['update' | 'duplicate', IUpdateTag] | void;
+export type TResultMissingEval = INewTag | void;
+
+interface IProcessXML {
+    attr?: string;
+    containerQuery?: string;
+    evalExistingEntry?: TEvalExistingEntry;
+    evalMissingEntry?: TEvalMissingEntry;
+    hidable?: boolean;
+    path: string;
+    step: number;
+    tagName: string;
+}
+
+export type TEvalExistingEntry = (
+    attrname: string,
+    migItem: IMigItem,
+    qItem: Element
+) => TResultExistingEval;
+
+export type TEvalMissingEntry = (originalToken: string, migItem: IMigItem) => TResultMissingEval;
+
+export async function processQueriedEntries(
+    migrationMap: IMigrationMap,
+    {
+        attr = 'name',
+        containerQuery = '*',
+        evalExistingEntry,
+        path,
+        step,
+        tagName,
+        evalMissingEntry,
+    }: IProcessXML
+) {
+    const doc = await FileIO.loadXML(path);
+
+    const containerElement =
+        (containerQuery && doc.querySelector(containerQuery)) || doc.documentElement;
+
+    migrationMap.forEach((migItem, originalToken) => {
+        migItem.step[step] = 'ignore';
+
+        const queryTiems = containerElement.querySelectorAll(
+            `${tagName}[${attr}="${originalToken}"]`
+        );
+
+        if (evalMissingEntry) {
+            const addinOptions = evalMissingEntry(originalToken, migItem);
+
+            if (queryTiems.length == 0 && containerElement && addinOptions) {
+                DOM.addEntry(containerElement, addinOptions);
+                migItem.step[step] = 'add';
+                return;
+            }
+        }
+
+        if (evalExistingEntry)
+            queryTiems.forEach((qEl) => {
+                const attrName = qEl.getAttribute(attr);
+                const migItem = migrationMap.get(attrName || '');
+
+                if (!attrName || !migItem) return;
+
+                const updateOptions = evalExistingEntry(attrName, migItem, qEl);
+
+                if (!updateOptions) return;
+
+                const [processType, processOptions] = updateOptions;
+
+                switch (processType) {
+                    case 'update':
+                        if (DOM.updateElement(qEl, processOptions)) migItem.step[step] = 'update';
+                        break;
+
+                    case 'duplicate':
+                        if (DOM.duplicateEntryWithChange(qEl, processOptions))
+                            migItem.step[step] = 'duplicate';
+                        break;
+                }
+            });
+    });
+
+    await FileIO.saveFile(doc.documentElement.outerHTML, path);
+}
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/rootPath.ts b/packages/SystemUI/scripts/token_alignment/helpers/rootPath.ts
new file mode 100644
index 0000000..2c6f632
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/rootPath.ts
@@ -0,0 +1,21 @@
+// Copyright 2022 Google LLC
+
+// 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.import { exec } from 'child_process';
+
+if (!process?.env?.ANDROID_BUILD_TOP) {
+    console.log(
+        "Error: Couldn't find 'ANDROID_BUILD_TOP' environment variable. Make sure to run 'lunch' in this terminal"
+    );
+}
+
+export const repoPath = process?.env?.ANDROID_BUILD_TOP;
diff --git a/packages/SystemUI/scripts/token_alignment/helpers/textFuncs.ts b/packages/SystemUI/scripts/token_alignment/helpers/textFuncs.ts
new file mode 100644
index 0000000..6679c5a
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/helpers/textFuncs.ts
@@ -0,0 +1,27 @@
+// Copyright 2022 Google LLC
+
+// 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.import { exec } from 'child_process';
+
+export function groupReplace(src: string, replaceMap: Map<string, string>, pattern: string) {
+    const fullPattern = pattern.replace('#group#', [...replaceMap.keys()].join('|'));
+
+    const regEx = new RegExp(fullPattern, 'g');
+
+    ''.replace;
+
+    return src.replace(regEx, (...args) => {
+        //match, ...matches, offset, string, groups
+        const [match, key] = args as string[];
+        return match.replace(key, replaceMap.get(key) || '');
+    });
+}
diff --git a/packages/SystemUI/scripts/token_alignment/index.ts b/packages/SystemUI/scripts/token_alignment/index.ts
new file mode 100644
index 0000000..1b15e48
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/index.ts
@@ -0,0 +1,240 @@
+// Copyright 2022 Google LLC
+
+// 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.import { exec } from 'child_process';
+
+import DOM from './helpers/DOMFuncs';
+import { FileIO } from './helpers/FileIO';
+import { loadMIgrationList } from './helpers/migrationList';
+import { processQueriedEntries, TEvalExistingEntry } from './helpers/processXML';
+import { repoPath } from './helpers/rootPath';
+import { groupReplace } from './helpers/textFuncs';
+
+async function init() {
+    const migrationMap = await loadMIgrationList();
+    const basePath = `${repoPath}/../tm-qpr-dev/frameworks/base/core/res/res/values/`;
+
+    await processQueriedEntries(migrationMap, {
+        containerQuery: 'declare-styleable[name="Theme"]',
+        hidable: true,
+        path: `${basePath}attrs.xml`,
+        step: 0,
+        tagName: 'attr',
+        evalExistingEntry: (_attrValue, migItem, qItem) => {
+            const { hidden, textContent: currentComment } = DOM.getElementComment(qItem);
+
+            if (hidden) migItem.isHidden = hidden;
+
+            const { newComment } = migItem;
+            return [
+                hidden ? 'update' : 'duplicate',
+                {
+                    attrs: { name: migItem.replaceToken },
+                    ...(newComment
+                        ? { comment: `${newComment} @hide ` }
+                        : currentComment
+                        ? { comment: hidden ? currentComment : `${currentComment} @hide ` }
+                        : {}),
+                },
+            ];
+        },
+        evalMissingEntry: (_originalToken, { replaceToken, newComment }) => {
+            return {
+                tagName: 'attr',
+                attrs: {
+                    name: replaceToken,
+                    format: 'color',
+                },
+                comment: `${newComment} @hide `,
+            };
+        },
+    });
+
+    // only update all existing entries
+    await processQueriedEntries(migrationMap, {
+        tagName: 'item',
+        path: `${basePath}themes_device_defaults.xml`,
+        containerQuery: 'resources',
+        step: 2,
+        evalExistingEntry: (_attrValue, { isHidden, replaceToken, step }, _qItem) => {
+            if (step[0] != 'ignore')
+                return [
+                    isHidden ? 'update' : 'duplicate',
+                    {
+                        attrs: { name: replaceToken },
+                    },
+                ];
+        },
+    });
+
+    // add missing entries on specific container
+    await processQueriedEntries(migrationMap, {
+        tagName: 'item',
+        path: `${basePath}themes_device_defaults.xml`,
+        containerQuery: 'resources style[parent="Theme.Material"]',
+        step: 3,
+        evalMissingEntry: (originalToken, { newDefaultValue, replaceToken }) => {
+            return {
+                tagName: 'item',
+                content: newDefaultValue,
+                attrs: {
+                    name: replaceToken,
+                },
+            };
+        },
+    });
+
+    const evalExistingEntry: TEvalExistingEntry = (_attrValue, { replaceToken, step }, _qItem) => {
+        if (step[0] == 'update')
+            return [
+                'update',
+                {
+                    attrs: { name: replaceToken },
+                },
+            ];
+    };
+
+    await processQueriedEntries(migrationMap, {
+        tagName: 'item',
+        containerQuery: 'resources',
+        path: `${basePath}../values-night/themes_device_defaults.xml`,
+        step: 4,
+        evalExistingEntry,
+    });
+
+    await processQueriedEntries(migrationMap, {
+        tagName: 'java-symbol',
+        path: `${basePath}symbols.xml`,
+        containerQuery: 'resources',
+        step: 5,
+        evalExistingEntry,
+    });
+
+    // update attributes on tracked XML files
+    {
+        const searchAttrs = [
+            'android:color',
+            'android:indeterminateTint',
+            'app:tint',
+            'app:backgroundTint',
+            'android:background',
+            'android:tint',
+            'android:drawableTint',
+            'android:textColor',
+            'android:fillColor',
+            'android:startColor',
+            'android:endColor',
+            'name',
+            'ns1:color',
+        ];
+
+        const filtered = new Map(
+            [...migrationMap]
+                .filter(([_originalToken, { step }]) => step[0] == 'update')
+                .map(([originalToken, { replaceToken }]) => [originalToken, replaceToken])
+        );
+
+        const query =
+            searchAttrs.map((str) => `*[${str}]`).join(',') +
+            [...filtered.keys()].map((originalToken) => `item[name*="${originalToken}"]`).join(',');
+
+        const trackedFiles = await FileIO.loadFileList(
+            `${__dirname}/resources/whitelist/xmls1.json`
+        );
+
+        const promises = trackedFiles.map(async (locaFilePath) => {
+            const filePath = `${repoPath}/${locaFilePath}`;
+
+            const doc = await FileIO.loadXML(filePath);
+            const docUpdated = DOM.replaceStringInAttributeValueOnQueried(
+                doc.documentElement,
+                query,
+                searchAttrs,
+                filtered
+            );
+            if (docUpdated) {
+                await FileIO.saveFile(DOM.XMLDocToString(doc), filePath);
+            } else {
+                console.warn(`Failed to update tracked file: '${locaFilePath}'`);
+            }
+        });
+        await Promise.all(promises);
+    }
+
+    // updates tag content on tracked files
+    {
+        const searchPrefixes = ['?android:attr/', '?androidprv:attr/'];
+        const filtered = searchPrefixes
+            .reduce<Array<[string, string]>>((acc, prefix) => {
+                return [
+                    ...acc,
+                    ...[...migrationMap.entries()]
+                        .filter(([_originalToken, { step }]) => step[0] == 'update')
+                        .map(
+                            ([originalToken, { replaceToken }]) =>
+                                [`${prefix}${originalToken}`, `${prefix}${replaceToken}`] as [
+                                    string,
+                                    string
+                                ]
+                        ),
+                ];
+            }, [])
+            .sort((a, b) => b[0].length - a[0].length);
+
+        const trackedFiles = await FileIO.loadFileList(
+            `${__dirname}/resources/whitelist/xmls2.json`
+        );
+
+        const promises = trackedFiles.map(async (locaFilePath) => {
+            const filePath = `${repoPath}/${locaFilePath}`;
+            const doc = await FileIO.loadXML(filePath);
+            const docUpdated = DOM.replaceContentTextOnQueried(
+                doc.documentElement,
+                'item, color',
+                filtered
+            );
+            if (docUpdated) {
+                await FileIO.saveFile(DOM.XMLDocToString(doc), filePath);
+            } else {
+                console.warn(`Failed to update tracked file: '${locaFilePath}'`);
+            }
+        });
+        await Promise.all(promises);
+    }
+
+    // replace imports on Java / Kotlin
+    {
+        const replaceMap = new Map(
+            [...migrationMap.entries()]
+                .filter(([_originalToken, { step }]) => step[0] == 'update')
+                .map(
+                    ([originalToken, { replaceToken }]) =>
+                        [originalToken, replaceToken] as [string, string]
+                )
+                .sort((a, b) => b[0].length - a[0].length)
+        );
+
+        const trackedFiles = await FileIO.loadFileList(
+            `${__dirname}/resources/whitelist/java.json`
+        );
+
+        const promises = trackedFiles.map(async (locaFilePath) => {
+            const filePath = `${repoPath}/${locaFilePath}`;
+            const fileContent = await FileIO.loadFileAsText(filePath);
+            const str = groupReplace(fileContent, replaceMap, 'R.attr.(#group#)(?![a-zA-Z])');
+            await FileIO.saveFile(str, filePath);
+        });
+        await Promise.all(promises);
+    }
+}
+
+init();
diff --git a/packages/SystemUI/scripts/token_alignment/package-lock.json b/packages/SystemUI/scripts/token_alignment/package-lock.json
new file mode 100644
index 0000000..da9edb3
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/package-lock.json
@@ -0,0 +1,3356 @@
+{
+    "name": "token_alignment",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+        "": {
+            "dependencies": {
+                "csv-parse": "^5.3.3",
+                "high5": "^1.0.0",
+                "jsdom": "^20.0.3"
+            },
+            "devDependencies": {
+                "@types/jsdom": "^20.0.1",
+                "@types/node": "^18.11.18",
+                "@typescript-eslint/eslint-plugin": "^5.48.0",
+                "eslint-config-prettier": "^8.6.0",
+                "eslint-plugin-import": "^2.26.0",
+                "eslint-plugin-prettier": "^4.2.1",
+                "eslint-plugin-simple-import-sort": "^8.0.0",
+                "ts-node": "^10.9.1",
+                "typescript": "^4.9.4"
+            }
+        },
+        "node_modules/@cspotcode/source-map-support": {
+            "version": "0.8.1",
+            "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+            "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+            "dev": true,
+            "dependencies": {
+                "@jridgewell/trace-mapping": "0.3.9"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/@eslint/eslintrc": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
+            "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "ajv": "^6.12.4",
+                "debug": "^4.3.2",
+                "espree": "^9.4.0",
+                "globals": "^13.19.0",
+                "ignore": "^5.2.0",
+                "import-fresh": "^3.2.1",
+                "js-yaml": "^4.1.0",
+                "minimatch": "^3.1.2",
+                "strip-json-comments": "^3.1.1"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            }
+        },
+        "node_modules/@humanwhocodes/config-array": {
+            "version": "0.11.8",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
+            "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@humanwhocodes/object-schema": "^1.2.1",
+                "debug": "^4.1.1",
+                "minimatch": "^3.0.5"
+            },
+            "engines": {
+                "node": ">=10.10.0"
+            }
+        },
+        "node_modules/@humanwhocodes/module-importer": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+            "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=12.22"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/nzakas"
+            }
+        },
+        "node_modules/@humanwhocodes/object-schema": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+            "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/@jridgewell/resolve-uri": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+            "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+            "dev": true,
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/@jridgewell/sourcemap-codec": {
+            "version": "1.4.14",
+            "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+            "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+            "dev": true
+        },
+        "node_modules/@jridgewell/trace-mapping": {
+            "version": "0.3.9",
+            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+            "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+            "dev": true,
+            "dependencies": {
+                "@jridgewell/resolve-uri": "^3.0.3",
+                "@jridgewell/sourcemap-codec": "^1.4.10"
+            }
+        },
+        "node_modules/@nodelib/fs.scandir": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+            "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+            "dev": true,
+            "dependencies": {
+                "@nodelib/fs.stat": "2.0.5",
+                "run-parallel": "^1.1.9"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/@nodelib/fs.stat": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+            "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+            "dev": true,
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/@nodelib/fs.walk": {
+            "version": "1.2.8",
+            "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+            "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+            "dev": true,
+            "dependencies": {
+                "@nodelib/fs.scandir": "2.1.5",
+                "fastq": "^1.6.0"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/@tootallnate/once": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+            "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+            "engines": {
+                "node": ">= 10"
+            }
+        },
+        "node_modules/@tsconfig/node10": {
+            "version": "1.0.9",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+            "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+            "dev": true
+        },
+        "node_modules/@tsconfig/node12": {
+            "version": "1.0.11",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+            "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+            "dev": true
+        },
+        "node_modules/@tsconfig/node14": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+            "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+            "dev": true
+        },
+        "node_modules/@tsconfig/node16": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+            "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
+            "dev": true
+        },
+        "node_modules/@types/jsdom": {
+            "version": "20.0.1",
+            "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
+            "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==",
+            "dev": true,
+            "dependencies": {
+                "@types/node": "*",
+                "@types/tough-cookie": "*",
+                "parse5": "^7.0.0"
+            }
+        },
+        "node_modules/@types/json-schema": {
+            "version": "7.0.11",
+            "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+            "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
+            "dev": true
+        },
+        "node_modules/@types/json5": {
+            "version": "0.0.29",
+            "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+            "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+            "dev": true
+        },
+        "node_modules/@types/node": {
+            "version": "18.11.18",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
+            "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==",
+            "dev": true
+        },
+        "node_modules/@types/semver": {
+            "version": "7.3.13",
+            "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
+            "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
+            "dev": true
+        },
+        "node_modules/@types/tough-cookie": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
+            "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==",
+            "dev": true
+        },
+        "node_modules/@typescript-eslint/eslint-plugin": {
+            "version": "5.48.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz",
+            "integrity": "sha512-SVLafp0NXpoJY7ut6VFVUU9I+YeFsDzeQwtK0WZ+xbRN3mtxJ08je+6Oi2N89qDn087COdO0u3blKZNv9VetRQ==",
+            "dev": true,
+            "dependencies": {
+                "@typescript-eslint/scope-manager": "5.48.0",
+                "@typescript-eslint/type-utils": "5.48.0",
+                "@typescript-eslint/utils": "5.48.0",
+                "debug": "^4.3.4",
+                "ignore": "^5.2.0",
+                "natural-compare-lite": "^1.4.0",
+                "regexpp": "^3.2.0",
+                "semver": "^7.3.7",
+                "tsutils": "^3.21.0"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "@typescript-eslint/parser": "^5.0.0",
+                "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@typescript-eslint/parser": {
+            "version": "5.48.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz",
+            "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@typescript-eslint/scope-manager": "5.48.0",
+                "@typescript-eslint/types": "5.48.0",
+                "@typescript-eslint/typescript-estree": "5.48.0",
+                "debug": "^4.3.4"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@typescript-eslint/scope-manager": {
+            "version": "5.48.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz",
+            "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==",
+            "dev": true,
+            "dependencies": {
+                "@typescript-eslint/types": "5.48.0",
+                "@typescript-eslint/visitor-keys": "5.48.0"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            }
+        },
+        "node_modules/@typescript-eslint/type-utils": {
+            "version": "5.48.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.0.tgz",
+            "integrity": "sha512-vbtPO5sJyFjtHkGlGK4Sthmta0Bbls4Onv0bEqOGm7hP9h8UpRsHJwsrCiWtCUndTRNQO/qe6Ijz9rnT/DB+7g==",
+            "dev": true,
+            "dependencies": {
+                "@typescript-eslint/typescript-estree": "5.48.0",
+                "@typescript-eslint/utils": "5.48.0",
+                "debug": "^4.3.4",
+                "tsutils": "^3.21.0"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "*"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@typescript-eslint/types": {
+            "version": "5.48.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz",
+            "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==",
+            "dev": true,
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            }
+        },
+        "node_modules/@typescript-eslint/typescript-estree": {
+            "version": "5.48.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz",
+            "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==",
+            "dev": true,
+            "dependencies": {
+                "@typescript-eslint/types": "5.48.0",
+                "@typescript-eslint/visitor-keys": "5.48.0",
+                "debug": "^4.3.4",
+                "globby": "^11.1.0",
+                "is-glob": "^4.0.3",
+                "semver": "^7.3.7",
+                "tsutils": "^3.21.0"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@typescript-eslint/utils": {
+            "version": "5.48.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.0.tgz",
+            "integrity": "sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==",
+            "dev": true,
+            "dependencies": {
+                "@types/json-schema": "^7.0.9",
+                "@types/semver": "^7.3.12",
+                "@typescript-eslint/scope-manager": "5.48.0",
+                "@typescript-eslint/types": "5.48.0",
+                "@typescript-eslint/typescript-estree": "5.48.0",
+                "eslint-scope": "^5.1.1",
+                "eslint-utils": "^3.0.0",
+                "semver": "^7.3.7"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+            }
+        },
+        "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
+            "version": "5.1.1",
+            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+            "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+            "dev": true,
+            "dependencies": {
+                "esrecurse": "^4.3.0",
+                "estraverse": "^4.1.1"
+            },
+            "engines": {
+                "node": ">=8.0.0"
+            }
+        },
+        "node_modules/@typescript-eslint/utils/node_modules/estraverse": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+            "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+            "dev": true,
+            "engines": {
+                "node": ">=4.0"
+            }
+        },
+        "node_modules/@typescript-eslint/visitor-keys": {
+            "version": "5.48.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz",
+            "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==",
+            "dev": true,
+            "dependencies": {
+                "@typescript-eslint/types": "5.48.0",
+                "eslint-visitor-keys": "^3.3.0"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            }
+        },
+        "node_modules/abab": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
+            "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="
+        },
+        "node_modules/acorn": {
+            "version": "8.8.1",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
+            "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
+            "bin": {
+                "acorn": "bin/acorn"
+            },
+            "engines": {
+                "node": ">=0.4.0"
+            }
+        },
+        "node_modules/acorn-globals": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+            "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+            "dependencies": {
+                "acorn": "^8.1.0",
+                "acorn-walk": "^8.0.2"
+            }
+        },
+        "node_modules/acorn-jsx": {
+            "version": "5.3.2",
+            "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+            "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+            "dev": true,
+            "peer": true,
+            "peerDependencies": {
+                "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+            }
+        },
+        "node_modules/acorn-walk": {
+            "version": "8.2.0",
+            "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+            "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+            "engines": {
+                "node": ">=0.4.0"
+            }
+        },
+        "node_modules/agent-base": {
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+            "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+            "dependencies": {
+                "debug": "4"
+            },
+            "engines": {
+                "node": ">= 6.0.0"
+            }
+        },
+        "node_modules/ajv": {
+            "version": "6.12.6",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+            "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "fast-deep-equal": "^3.1.1",
+                "fast-json-stable-stringify": "^2.0.0",
+                "json-schema-traverse": "^0.4.1",
+                "uri-js": "^4.2.2"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/epoberezkin"
+            }
+        },
+        "node_modules/ansi-regex": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/ansi-styles": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "color-convert": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+            }
+        },
+        "node_modules/arg": {
+            "version": "4.1.3",
+            "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+            "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+            "dev": true
+        },
+        "node_modules/argparse": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+            "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/array-includes": {
+            "version": "3.1.6",
+            "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+            "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.1.4",
+                "es-abstract": "^1.20.4",
+                "get-intrinsic": "^1.1.3",
+                "is-string": "^1.0.7"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/array-union": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+            "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/array.prototype.flat": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
+            "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.1.4",
+                "es-abstract": "^1.20.4",
+                "es-shim-unscopables": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/asynckit": {
+            "version": "0.4.0",
+            "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+            "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+        },
+        "node_modules/available-typed-arrays": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+            "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+            "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/balanced-match": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+            "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+            "dev": true
+        },
+        "node_modules/brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dev": true,
+            "dependencies": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "node_modules/braces": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+            "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+            "dev": true,
+            "dependencies": {
+                "fill-range": "^7.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/call-bind": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+            "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+            "dev": true,
+            "dependencies": {
+                "function-bind": "^1.1.1",
+                "get-intrinsic": "^1.0.2"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/callsites": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+            "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/chalk": {
+            "version": "4.1.2",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+            "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/chalk?sponsor=1"
+            }
+        },
+        "node_modules/color-convert": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+            "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "color-name": "~1.1.4"
+            },
+            "engines": {
+                "node": ">=7.0.0"
+            }
+        },
+        "node_modules/color-name": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+            "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/combined-stream": {
+            "version": "1.0.8",
+            "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+            "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+            "dependencies": {
+                "delayed-stream": "~1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.8"
+            }
+        },
+        "node_modules/concat-map": {
+            "version": "0.0.1",
+            "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+            "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+            "dev": true
+        },
+        "node_modules/create-require": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+            "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+            "dev": true
+        },
+        "node_modules/cross-spawn": {
+            "version": "7.0.3",
+            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+            "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "path-key": "^3.1.0",
+                "shebang-command": "^2.0.0",
+                "which": "^2.0.1"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/cssom": {
+            "version": "0.5.0",
+            "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
+            "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="
+        },
+        "node_modules/cssstyle": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
+            "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+            "dependencies": {
+                "cssom": "~0.3.6"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/cssstyle/node_modules/cssom": {
+            "version": "0.3.8",
+            "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+            "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
+        },
+        "node_modules/csv-parse": {
+            "version": "5.3.3",
+            "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.3.3.tgz",
+            "integrity": "sha512-kEWkAPleNEdhFNkHQpFHu9RYPogsFj3dx6bCxL847fsiLgidzWg0z/O0B1kVWMJUc5ky64zGp18LX2T3DQrOfw=="
+        },
+        "node_modules/data-urls": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
+            "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+            "dependencies": {
+                "abab": "^2.0.6",
+                "whatwg-mimetype": "^3.0.0",
+                "whatwg-url": "^11.0.0"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/decimal.js": {
+            "version": "10.4.3",
+            "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+            "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
+        },
+        "node_modules/deep-is": {
+            "version": "0.1.4",
+            "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+            "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
+        },
+        "node_modules/define-properties": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
+            "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+            "dev": true,
+            "dependencies": {
+                "has-property-descriptors": "^1.0.0",
+                "object-keys": "^1.1.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/delayed-stream": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+            "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+            "engines": {
+                "node": ">=0.4.0"
+            }
+        },
+        "node_modules/diff": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+            "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.3.1"
+            }
+        },
+        "node_modules/dir-glob": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+            "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+            "dev": true,
+            "dependencies": {
+                "path-type": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/doctrine": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+            "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "esutils": "^2.0.2"
+            },
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/domexception": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
+            "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
+            "dependencies": {
+                "webidl-conversions": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/entities": {
+            "version": "4.4.0",
+            "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
+            "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
+            "engines": {
+                "node": ">=0.12"
+            },
+            "funding": {
+                "url": "https://github.com/fb55/entities?sponsor=1"
+            }
+        },
+        "node_modules/es-abstract": {
+            "version": "1.21.0",
+            "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.0.tgz",
+            "integrity": "sha512-GUGtW7eXQay0c+PRq0sGIKSdaBorfVqsCMhGHo4elP7YVqZu9nCZS4UkK4gv71gOWNMra/PaSKD3ao1oWExO0g==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "es-set-tostringtag": "^2.0.0",
+                "es-to-primitive": "^1.2.1",
+                "function-bind": "^1.1.1",
+                "function.prototype.name": "^1.1.5",
+                "get-intrinsic": "^1.1.3",
+                "get-symbol-description": "^1.0.0",
+                "globalthis": "^1.0.3",
+                "gopd": "^1.0.1",
+                "has": "^1.0.3",
+                "has-property-descriptors": "^1.0.0",
+                "has-proto": "^1.0.1",
+                "has-symbols": "^1.0.3",
+                "internal-slot": "^1.0.4",
+                "is-array-buffer": "^3.0.0",
+                "is-callable": "^1.2.7",
+                "is-negative-zero": "^2.0.2",
+                "is-regex": "^1.1.4",
+                "is-shared-array-buffer": "^1.0.2",
+                "is-string": "^1.0.7",
+                "is-typed-array": "^1.1.10",
+                "is-weakref": "^1.0.2",
+                "object-inspect": "^1.12.2",
+                "object-keys": "^1.1.1",
+                "object.assign": "^4.1.4",
+                "regexp.prototype.flags": "^1.4.3",
+                "safe-regex-test": "^1.0.0",
+                "string.prototype.trimend": "^1.0.6",
+                "string.prototype.trimstart": "^1.0.6",
+                "typed-array-length": "^1.0.4",
+                "unbox-primitive": "^1.0.2",
+                "which-typed-array": "^1.1.9"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/es-set-tostringtag": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
+            "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+            "dev": true,
+            "dependencies": {
+                "get-intrinsic": "^1.1.3",
+                "has": "^1.0.3",
+                "has-tostringtag": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
+        "node_modules/es-shim-unscopables": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+            "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+            "dev": true,
+            "dependencies": {
+                "has": "^1.0.3"
+            }
+        },
+        "node_modules/es-to-primitive": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+            "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+            "dev": true,
+            "dependencies": {
+                "is-callable": "^1.1.4",
+                "is-date-object": "^1.0.1",
+                "is-symbol": "^1.0.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/escape-string-regexp": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+            "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/escodegen": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
+            "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
+            "dependencies": {
+                "esprima": "^4.0.1",
+                "estraverse": "^5.2.0",
+                "esutils": "^2.0.2",
+                "optionator": "^0.8.1"
+            },
+            "bin": {
+                "escodegen": "bin/escodegen.js",
+                "esgenerate": "bin/esgenerate.js"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "optionalDependencies": {
+                "source-map": "~0.6.1"
+            }
+        },
+        "node_modules/eslint": {
+            "version": "8.31.0",
+            "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz",
+            "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "@eslint/eslintrc": "^1.4.1",
+                "@humanwhocodes/config-array": "^0.11.8",
+                "@humanwhocodes/module-importer": "^1.0.1",
+                "@nodelib/fs.walk": "^1.2.8",
+                "ajv": "^6.10.0",
+                "chalk": "^4.0.0",
+                "cross-spawn": "^7.0.2",
+                "debug": "^4.3.2",
+                "doctrine": "^3.0.0",
+                "escape-string-regexp": "^4.0.0",
+                "eslint-scope": "^7.1.1",
+                "eslint-utils": "^3.0.0",
+                "eslint-visitor-keys": "^3.3.0",
+                "espree": "^9.4.0",
+                "esquery": "^1.4.0",
+                "esutils": "^2.0.2",
+                "fast-deep-equal": "^3.1.3",
+                "file-entry-cache": "^6.0.1",
+                "find-up": "^5.0.0",
+                "glob-parent": "^6.0.2",
+                "globals": "^13.19.0",
+                "grapheme-splitter": "^1.0.4",
+                "ignore": "^5.2.0",
+                "import-fresh": "^3.0.0",
+                "imurmurhash": "^0.1.4",
+                "is-glob": "^4.0.0",
+                "is-path-inside": "^3.0.3",
+                "js-sdsl": "^4.1.4",
+                "js-yaml": "^4.1.0",
+                "json-stable-stringify-without-jsonify": "^1.0.1",
+                "levn": "^0.4.1",
+                "lodash.merge": "^4.6.2",
+                "minimatch": "^3.1.2",
+                "natural-compare": "^1.4.0",
+                "optionator": "^0.9.1",
+                "regexpp": "^3.2.0",
+                "strip-ansi": "^6.0.1",
+                "strip-json-comments": "^3.1.0",
+                "text-table": "^0.2.0"
+            },
+            "bin": {
+                "eslint": "bin/eslint.js"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            }
+        },
+        "node_modules/eslint-config-prettier": {
+            "version": "8.6.0",
+            "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz",
+            "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==",
+            "dev": true,
+            "bin": {
+                "eslint-config-prettier": "bin/cli.js"
+            },
+            "peerDependencies": {
+                "eslint": ">=7.0.0"
+            }
+        },
+        "node_modules/eslint-import-resolver-node": {
+            "version": "0.3.6",
+            "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz",
+            "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==",
+            "dev": true,
+            "dependencies": {
+                "debug": "^3.2.7",
+                "resolve": "^1.20.0"
+            }
+        },
+        "node_modules/eslint-import-resolver-node/node_modules/debug": {
+            "version": "3.2.7",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+            "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+            "dev": true,
+            "dependencies": {
+                "ms": "^2.1.1"
+            }
+        },
+        "node_modules/eslint-module-utils": {
+            "version": "2.7.4",
+            "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz",
+            "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==",
+            "dev": true,
+            "dependencies": {
+                "debug": "^3.2.7"
+            },
+            "engines": {
+                "node": ">=4"
+            },
+            "peerDependenciesMeta": {
+                "eslint": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/eslint-module-utils/node_modules/debug": {
+            "version": "3.2.7",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+            "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+            "dev": true,
+            "dependencies": {
+                "ms": "^2.1.1"
+            }
+        },
+        "node_modules/eslint-plugin-import": {
+            "version": "2.26.0",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
+            "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
+            "dev": true,
+            "dependencies": {
+                "array-includes": "^3.1.4",
+                "array.prototype.flat": "^1.2.5",
+                "debug": "^2.6.9",
+                "doctrine": "^2.1.0",
+                "eslint-import-resolver-node": "^0.3.6",
+                "eslint-module-utils": "^2.7.3",
+                "has": "^1.0.3",
+                "is-core-module": "^2.8.1",
+                "is-glob": "^4.0.3",
+                "minimatch": "^3.1.2",
+                "object.values": "^1.1.5",
+                "resolve": "^1.22.0",
+                "tsconfig-paths": "^3.14.1"
+            },
+            "engines": {
+                "node": ">=4"
+            },
+            "peerDependencies": {
+                "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
+            }
+        },
+        "node_modules/eslint-plugin-import/node_modules/debug": {
+            "version": "2.6.9",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+            "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+            "dev": true,
+            "dependencies": {
+                "ms": "2.0.0"
+            }
+        },
+        "node_modules/eslint-plugin-import/node_modules/doctrine": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+            "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+            "dev": true,
+            "dependencies": {
+                "esutils": "^2.0.2"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/eslint-plugin-import/node_modules/ms": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+            "dev": true
+        },
+        "node_modules/eslint-plugin-prettier": {
+            "version": "4.2.1",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
+            "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==",
+            "dev": true,
+            "dependencies": {
+                "prettier-linter-helpers": "^1.0.0"
+            },
+            "engines": {
+                "node": ">=12.0.0"
+            },
+            "peerDependencies": {
+                "eslint": ">=7.28.0",
+                "prettier": ">=2.0.0"
+            },
+            "peerDependenciesMeta": {
+                "eslint-config-prettier": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/eslint-plugin-simple-import-sort": {
+            "version": "8.0.0",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-8.0.0.tgz",
+            "integrity": "sha512-bXgJQ+lqhtQBCuWY/FUWdB27j4+lqcvXv5rUARkzbeWLwea+S5eBZEQrhnO+WgX3ZoJHVj0cn943iyXwByHHQw==",
+            "dev": true,
+            "peerDependencies": {
+                "eslint": ">=5.0.0"
+            }
+        },
+        "node_modules/eslint-scope": {
+            "version": "7.1.1",
+            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+            "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "esrecurse": "^4.3.0",
+                "estraverse": "^5.2.0"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            }
+        },
+        "node_modules/eslint-utils": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+            "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+            "dev": true,
+            "dependencies": {
+                "eslint-visitor-keys": "^2.0.0"
+            },
+            "engines": {
+                "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/mysticatea"
+            },
+            "peerDependencies": {
+                "eslint": ">=5"
+            }
+        },
+        "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+            "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+            "dev": true,
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/eslint-visitor-keys": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+            "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+            "dev": true,
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            }
+        },
+        "node_modules/eslint/node_modules/levn": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+            "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "prelude-ls": "^1.2.1",
+                "type-check": "~0.4.0"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/eslint/node_modules/optionator": {
+            "version": "0.9.1",
+            "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+            "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "deep-is": "^0.1.3",
+                "fast-levenshtein": "^2.0.6",
+                "levn": "^0.4.1",
+                "prelude-ls": "^1.2.1",
+                "type-check": "^0.4.0",
+                "word-wrap": "^1.2.3"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/eslint/node_modules/prelude-ls": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+            "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/eslint/node_modules/type-check": {
+            "version": "0.4.0",
+            "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+            "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "prelude-ls": "^1.2.1"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/espree": {
+            "version": "9.4.1",
+            "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
+            "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "acorn": "^8.8.0",
+                "acorn-jsx": "^5.3.2",
+                "eslint-visitor-keys": "^3.3.0"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            }
+        },
+        "node_modules/esprima": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+            "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+            "bin": {
+                "esparse": "bin/esparse.js",
+                "esvalidate": "bin/esvalidate.js"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/esquery": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+            "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "estraverse": "^5.1.0"
+            },
+            "engines": {
+                "node": ">=0.10"
+            }
+        },
+        "node_modules/esrecurse": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+            "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+            "dev": true,
+            "dependencies": {
+                "estraverse": "^5.2.0"
+            },
+            "engines": {
+                "node": ">=4.0"
+            }
+        },
+        "node_modules/estraverse": {
+            "version": "5.3.0",
+            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+            "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+            "engines": {
+                "node": ">=4.0"
+            }
+        },
+        "node_modules/esutils": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+            "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/fast-deep-equal": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/fast-diff": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+            "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+            "dev": true
+        },
+        "node_modules/fast-glob": {
+            "version": "3.2.12",
+            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+            "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+            "dev": true,
+            "dependencies": {
+                "@nodelib/fs.stat": "^2.0.2",
+                "@nodelib/fs.walk": "^1.2.3",
+                "glob-parent": "^5.1.2",
+                "merge2": "^1.3.0",
+                "micromatch": "^4.0.4"
+            },
+            "engines": {
+                "node": ">=8.6.0"
+            }
+        },
+        "node_modules/fast-glob/node_modules/glob-parent": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+            "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+            "dev": true,
+            "dependencies": {
+                "is-glob": "^4.0.1"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/fast-json-stable-stringify": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+            "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/fast-levenshtein": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+            "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
+        },
+        "node_modules/fastq": {
+            "version": "1.15.0",
+            "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+            "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+            "dev": true,
+            "dependencies": {
+                "reusify": "^1.0.4"
+            }
+        },
+        "node_modules/file-entry-cache": {
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+            "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "flat-cache": "^3.0.4"
+            },
+            "engines": {
+                "node": "^10.12.0 || >=12.0.0"
+            }
+        },
+        "node_modules/fill-range": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+            "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+            "dev": true,
+            "dependencies": {
+                "to-regex-range": "^5.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/find-up": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+            "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "locate-path": "^6.0.0",
+                "path-exists": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/flat-cache": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+            "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "flatted": "^3.1.0",
+                "rimraf": "^3.0.2"
+            },
+            "engines": {
+                "node": "^10.12.0 || >=12.0.0"
+            }
+        },
+        "node_modules/flatted": {
+            "version": "3.2.7",
+            "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+            "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/for-each": {
+            "version": "0.3.3",
+            "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+            "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+            "dev": true,
+            "dependencies": {
+                "is-callable": "^1.1.3"
+            }
+        },
+        "node_modules/form-data": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+            "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+            "dependencies": {
+                "asynckit": "^0.4.0",
+                "combined-stream": "^1.0.8",
+                "mime-types": "^2.1.12"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/fs.realpath": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+            "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/function-bind": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+            "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+            "dev": true
+        },
+        "node_modules/function.prototype.name": {
+            "version": "1.1.5",
+            "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+            "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.1.3",
+                "es-abstract": "^1.19.0",
+                "functions-have-names": "^1.2.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/functions-have-names": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+            "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/get-intrinsic": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
+            "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
+            "dev": true,
+            "dependencies": {
+                "function-bind": "^1.1.1",
+                "has": "^1.0.3",
+                "has-symbols": "^1.0.3"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/get-symbol-description": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
+            "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "get-intrinsic": "^1.1.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/glob": {
+            "version": "7.2.3",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+            "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "fs.realpath": "^1.0.0",
+                "inflight": "^1.0.4",
+                "inherits": "2",
+                "minimatch": "^3.1.1",
+                "once": "^1.3.0",
+                "path-is-absolute": "^1.0.0"
+            },
+            "engines": {
+                "node": "*"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/glob-parent": {
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+            "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "is-glob": "^4.0.3"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            }
+        },
+        "node_modules/globals": {
+            "version": "13.19.0",
+            "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz",
+            "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "type-fest": "^0.20.2"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/globalthis": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+            "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+            "dev": true,
+            "dependencies": {
+                "define-properties": "^1.1.3"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/globby": {
+            "version": "11.1.0",
+            "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+            "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+            "dev": true,
+            "dependencies": {
+                "array-union": "^2.1.0",
+                "dir-glob": "^3.0.1",
+                "fast-glob": "^3.2.9",
+                "ignore": "^5.2.0",
+                "merge2": "^1.4.1",
+                "slash": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/gopd": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+            "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+            "dev": true,
+            "dependencies": {
+                "get-intrinsic": "^1.1.3"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/grapheme-splitter": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+            "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/has": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+            "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+            "dev": true,
+            "dependencies": {
+                "function-bind": "^1.1.1"
+            },
+            "engines": {
+                "node": ">= 0.4.0"
+            }
+        },
+        "node_modules/has-bigints": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+            "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/has-flag": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+            "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/has-property-descriptors": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+            "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+            "dev": true,
+            "dependencies": {
+                "get-intrinsic": "^1.1.1"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/has-proto": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+            "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+            "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/has-symbols": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+            "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+            "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/has-tostringtag": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+            "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+            "dev": true,
+            "dependencies": {
+                "has-symbols": "^1.0.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/high5": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/high5/-/high5-1.0.0.tgz",
+            "integrity": "sha512-xucW/5M1hd+p6bj530wtRSKwqUQrgiIgOWepi4Di9abkonZaxhTDf0zrqqraxfZSXBcFSuc1/WVGBIlqSe1Hdw==",
+            "dependencies": {
+                "entities": "1.0"
+            }
+        },
+        "node_modules/high5/node_modules/entities": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+            "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ=="
+        },
+        "node_modules/html-encoding-sniffer": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
+            "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+            "dependencies": {
+                "whatwg-encoding": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/http-proxy-agent": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+            "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+            "dependencies": {
+                "@tootallnate/once": "2",
+                "agent-base": "6",
+                "debug": "4"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/https-proxy-agent": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+            "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+            "dependencies": {
+                "agent-base": "6",
+                "debug": "4"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/iconv-lite": {
+            "version": "0.6.3",
+            "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+            "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+            "dependencies": {
+                "safer-buffer": ">= 2.1.2 < 3.0.0"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/ignore": {
+            "version": "5.2.4",
+            "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+            "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+            "dev": true,
+            "engines": {
+                "node": ">= 4"
+            }
+        },
+        "node_modules/import-fresh": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+            "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "parent-module": "^1.0.0",
+                "resolve-from": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/imurmurhash": {
+            "version": "0.1.4",
+            "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+            "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=0.8.19"
+            }
+        },
+        "node_modules/inflight": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+            "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "once": "^1.3.0",
+                "wrappy": "1"
+            }
+        },
+        "node_modules/inherits": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+            "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/internal-slot": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz",
+            "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==",
+            "dev": true,
+            "dependencies": {
+                "get-intrinsic": "^1.1.3",
+                "has": "^1.0.3",
+                "side-channel": "^1.0.4"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
+        "node_modules/is-array-buffer": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz",
+            "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "get-intrinsic": "^1.1.3",
+                "is-typed-array": "^1.1.10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-bigint": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+            "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+            "dev": true,
+            "dependencies": {
+                "has-bigints": "^1.0.1"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-boolean-object": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+            "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "has-tostringtag": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-callable": {
+            "version": "1.2.7",
+            "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+            "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+            "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-core-module": {
+            "version": "2.11.0",
+            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+            "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+            "dev": true,
+            "dependencies": {
+                "has": "^1.0.3"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-date-object": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+            "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+            "dev": true,
+            "dependencies": {
+                "has-tostringtag": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-extglob": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+            "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/is-glob": {
+            "version": "4.0.3",
+            "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+            "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+            "dev": true,
+            "dependencies": {
+                "is-extglob": "^2.1.1"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/is-negative-zero": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+            "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+            "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-number": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+            "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.12.0"
+            }
+        },
+        "node_modules/is-number-object": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+            "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+            "dev": true,
+            "dependencies": {
+                "has-tostringtag": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-path-inside": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+            "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/is-potential-custom-element-name": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+            "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
+        },
+        "node_modules/is-regex": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+            "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "has-tostringtag": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-shared-array-buffer": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+            "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-string": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+            "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+            "dev": true,
+            "dependencies": {
+                "has-tostringtag": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-symbol": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+            "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+            "dev": true,
+            "dependencies": {
+                "has-symbols": "^1.0.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-typed-array": {
+            "version": "1.1.10",
+            "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
+            "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+            "dev": true,
+            "dependencies": {
+                "available-typed-arrays": "^1.0.5",
+                "call-bind": "^1.0.2",
+                "for-each": "^0.3.3",
+                "gopd": "^1.0.1",
+                "has-tostringtag": "^1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/is-weakref": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+            "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/isexe": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+            "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/js-sdsl": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
+            "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
+            "dev": true,
+            "peer": true,
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/js-sdsl"
+            }
+        },
+        "node_modules/js-yaml": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+            "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "argparse": "^2.0.1"
+            },
+            "bin": {
+                "js-yaml": "bin/js-yaml.js"
+            }
+        },
+        "node_modules/jsdom": {
+            "version": "20.0.3",
+            "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+            "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+            "dependencies": {
+                "abab": "^2.0.6",
+                "acorn": "^8.8.1",
+                "acorn-globals": "^7.0.0",
+                "cssom": "^0.5.0",
+                "cssstyle": "^2.3.0",
+                "data-urls": "^3.0.2",
+                "decimal.js": "^10.4.2",
+                "domexception": "^4.0.0",
+                "escodegen": "^2.0.0",
+                "form-data": "^4.0.0",
+                "html-encoding-sniffer": "^3.0.0",
+                "http-proxy-agent": "^5.0.0",
+                "https-proxy-agent": "^5.0.1",
+                "is-potential-custom-element-name": "^1.0.1",
+                "nwsapi": "^2.2.2",
+                "parse5": "^7.1.1",
+                "saxes": "^6.0.0",
+                "symbol-tree": "^3.2.4",
+                "tough-cookie": "^4.1.2",
+                "w3c-xmlserializer": "^4.0.0",
+                "webidl-conversions": "^7.0.0",
+                "whatwg-encoding": "^2.0.0",
+                "whatwg-mimetype": "^3.0.0",
+                "whatwg-url": "^11.0.0",
+                "ws": "^8.11.0",
+                "xml-name-validator": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=14"
+            },
+            "peerDependencies": {
+                "canvas": "^2.5.0"
+            },
+            "peerDependenciesMeta": {
+                "canvas": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/json-schema-traverse": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/json-stable-stringify-without-jsonify": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+            "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/json5": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+            "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+            "dev": true,
+            "dependencies": {
+                "minimist": "^1.2.0"
+            },
+            "bin": {
+                "json5": "lib/cli.js"
+            }
+        },
+        "node_modules/levn": {
+            "version": "0.3.0",
+            "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+            "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
+            "dependencies": {
+                "prelude-ls": "~1.1.2",
+                "type-check": "~0.3.2"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/locate-path": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+            "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "p-locate": "^5.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/lodash.merge": {
+            "version": "4.6.2",
+            "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+            "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/lru-cache": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+            "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+            "dev": true,
+            "dependencies": {
+                "yallist": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/make-error": {
+            "version": "1.3.6",
+            "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+            "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+            "dev": true
+        },
+        "node_modules/merge2": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+            "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+            "dev": true,
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/micromatch": {
+            "version": "4.0.5",
+            "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+            "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+            "dev": true,
+            "dependencies": {
+                "braces": "^3.0.2",
+                "picomatch": "^2.3.1"
+            },
+            "engines": {
+                "node": ">=8.6"
+            }
+        },
+        "node_modules/mime-db": {
+            "version": "1.52.0",
+            "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+            "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+            "engines": {
+                "node": ">= 0.6"
+            }
+        },
+        "node_modules/mime-types": {
+            "version": "2.1.35",
+            "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+            "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+            "dependencies": {
+                "mime-db": "1.52.0"
+            },
+            "engines": {
+                "node": ">= 0.6"
+            }
+        },
+        "node_modules/minimatch": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+            "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+            "dev": true,
+            "dependencies": {
+                "brace-expansion": "^1.1.7"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
+        "node_modules/minimist": {
+            "version": "1.2.7",
+            "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
+            "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "node_modules/natural-compare": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+            "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/natural-compare-lite": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+            "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
+            "dev": true
+        },
+        "node_modules/nwsapi": {
+            "version": "2.2.2",
+            "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
+            "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw=="
+        },
+        "node_modules/object-inspect": {
+            "version": "1.12.2",
+            "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+            "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
+            "dev": true,
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/object-keys": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+            "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+            "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
+        "node_modules/object.assign": {
+            "version": "4.1.4",
+            "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+            "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.1.4",
+                "has-symbols": "^1.0.3",
+                "object-keys": "^1.1.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/object.values": {
+            "version": "1.1.6",
+            "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+            "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.1.4",
+                "es-abstract": "^1.20.4"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/once": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+            "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "wrappy": "1"
+            }
+        },
+        "node_modules/optionator": {
+            "version": "0.8.3",
+            "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+            "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+            "dependencies": {
+                "deep-is": "~0.1.3",
+                "fast-levenshtein": "~2.0.6",
+                "levn": "~0.3.0",
+                "prelude-ls": "~1.1.2",
+                "type-check": "~0.3.2",
+                "word-wrap": "~1.2.3"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/p-limit": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+            "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "yocto-queue": "^0.1.0"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/p-locate": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+            "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "p-limit": "^3.0.2"
+            },
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/parent-module": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+            "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "callsites": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/parse5": {
+            "version": "7.1.2",
+            "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+            "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+            "dependencies": {
+                "entities": "^4.4.0"
+            },
+            "funding": {
+                "url": "https://github.com/inikulin/parse5?sponsor=1"
+            }
+        },
+        "node_modules/path-exists": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+            "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/path-is-absolute": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/path-key": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+            "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/path-parse": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+            "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+            "dev": true
+        },
+        "node_modules/path-type": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+            "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/picomatch": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+            "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+            "dev": true,
+            "engines": {
+                "node": ">=8.6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/jonschlinkert"
+            }
+        },
+        "node_modules/prelude-ls": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+            "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/prettier": {
+            "version": "2.8.2",
+            "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz",
+            "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==",
+            "dev": true,
+            "peer": true,
+            "bin": {
+                "prettier": "bin-prettier.js"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            },
+            "funding": {
+                "url": "https://github.com/prettier/prettier?sponsor=1"
+            }
+        },
+        "node_modules/prettier-linter-helpers": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+            "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+            "dev": true,
+            "dependencies": {
+                "fast-diff": "^1.1.2"
+            },
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/psl": {
+            "version": "1.9.0",
+            "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+            "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
+        },
+        "node_modules/punycode": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+            "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/querystringify": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+            "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
+        },
+        "node_modules/queue-microtask": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+            "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ]
+        },
+        "node_modules/regexp.prototype.flags": {
+            "version": "1.4.3",
+            "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+            "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.1.3",
+                "functions-have-names": "^1.2.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/regexpp": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+            "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/mysticatea"
+            }
+        },
+        "node_modules/requires-port": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+            "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
+        },
+        "node_modules/resolve": {
+            "version": "1.22.1",
+            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+            "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+            "dev": true,
+            "dependencies": {
+                "is-core-module": "^2.9.0",
+                "path-parse": "^1.0.7",
+                "supports-preserve-symlinks-flag": "^1.0.0"
+            },
+            "bin": {
+                "resolve": "bin/resolve"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/resolve-from": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+            "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/reusify": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+            "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+            "dev": true,
+            "engines": {
+                "iojs": ">=1.0.0",
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/rimraf": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+            "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "glob": "^7.1.3"
+            },
+            "bin": {
+                "rimraf": "bin.js"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/run-parallel": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+            "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ],
+            "dependencies": {
+                "queue-microtask": "^1.2.2"
+            }
+        },
+        "node_modules/safe-regex-test": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+            "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "get-intrinsic": "^1.1.3",
+                "is-regex": "^1.1.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/safer-buffer": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+            "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+        },
+        "node_modules/saxes": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+            "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+            "dependencies": {
+                "xmlchars": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=v12.22.7"
+            }
+        },
+        "node_modules/semver": {
+            "version": "7.3.8",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+            "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+            "dev": true,
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/shebang-command": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+            "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "shebang-regex": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/shebang-regex": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+            "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/side-channel": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+            "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.0",
+                "get-intrinsic": "^1.0.2",
+                "object-inspect": "^1.9.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/slash": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+            "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/source-map": {
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+            "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+            "optional": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/string.prototype.trimend": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+            "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.1.4",
+                "es-abstract": "^1.20.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/string.prototype.trimstart": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+            "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "define-properties": "^1.1.4",
+                "es-abstract": "^1.20.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/strip-ansi": {
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "ansi-regex": "^5.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/strip-bom": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+            "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/strip-json-comments": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+            "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/supports-color": {
+            "version": "7.2.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+            "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "has-flag": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/supports-preserve-symlinks-flag": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+            "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+            "dev": true,
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/symbol-tree": {
+            "version": "3.2.4",
+            "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+            "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
+        },
+        "node_modules/text-table": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/to-regex-range": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+            "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+            "dev": true,
+            "dependencies": {
+                "is-number": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=8.0"
+            }
+        },
+        "node_modules/tough-cookie": {
+            "version": "4.1.2",
+            "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
+            "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
+            "dependencies": {
+                "psl": "^1.1.33",
+                "punycode": "^2.1.1",
+                "universalify": "^0.2.0",
+                "url-parse": "^1.5.3"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/tr46": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
+            "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+            "dependencies": {
+                "punycode": "^2.1.1"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/ts-node": {
+            "version": "10.9.1",
+            "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+            "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+            "dev": true,
+            "dependencies": {
+                "@cspotcode/source-map-support": "^0.8.0",
+                "@tsconfig/node10": "^1.0.7",
+                "@tsconfig/node12": "^1.0.7",
+                "@tsconfig/node14": "^1.0.0",
+                "@tsconfig/node16": "^1.0.2",
+                "acorn": "^8.4.1",
+                "acorn-walk": "^8.1.1",
+                "arg": "^4.1.0",
+                "create-require": "^1.1.0",
+                "diff": "^4.0.1",
+                "make-error": "^1.1.1",
+                "v8-compile-cache-lib": "^3.0.1",
+                "yn": "3.1.1"
+            },
+            "bin": {
+                "ts-node": "dist/bin.js",
+                "ts-node-cwd": "dist/bin-cwd.js",
+                "ts-node-esm": "dist/bin-esm.js",
+                "ts-node-script": "dist/bin-script.js",
+                "ts-node-transpile-only": "dist/bin-transpile.js",
+                "ts-script": "dist/bin-script-deprecated.js"
+            },
+            "peerDependencies": {
+                "@swc/core": ">=1.2.50",
+                "@swc/wasm": ">=1.2.50",
+                "@types/node": "*",
+                "typescript": ">=2.7"
+            },
+            "peerDependenciesMeta": {
+                "@swc/core": {
+                    "optional": true
+                },
+                "@swc/wasm": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/tsconfig-paths": {
+            "version": "3.14.1",
+            "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
+            "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
+            "dev": true,
+            "dependencies": {
+                "@types/json5": "^0.0.29",
+                "json5": "^1.0.1",
+                "minimist": "^1.2.6",
+                "strip-bom": "^3.0.0"
+            }
+        },
+        "node_modules/tslib": {
+            "version": "1.14.1",
+            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+            "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+            "dev": true
+        },
+        "node_modules/tsutils": {
+            "version": "3.21.0",
+            "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+            "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+            "dev": true,
+            "dependencies": {
+                "tslib": "^1.8.1"
+            },
+            "engines": {
+                "node": ">= 6"
+            },
+            "peerDependencies": {
+                "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+            }
+        },
+        "node_modules/type-check": {
+            "version": "0.3.2",
+            "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+            "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
+            "dependencies": {
+                "prelude-ls": "~1.1.2"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/type-fest": {
+            "version": "0.20.2",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/typed-array-length": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
+            "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "for-each": "^0.3.3",
+                "is-typed-array": "^1.1.9"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/typescript": {
+            "version": "4.9.4",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
+            "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
+            "dev": true,
+            "bin": {
+                "tsc": "bin/tsc",
+                "tsserver": "bin/tsserver"
+            },
+            "engines": {
+                "node": ">=4.2.0"
+            }
+        },
+        "node_modules/unbox-primitive": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+            "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+            "dev": true,
+            "dependencies": {
+                "call-bind": "^1.0.2",
+                "has-bigints": "^1.0.2",
+                "has-symbols": "^1.0.3",
+                "which-boxed-primitive": "^1.0.2"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/universalify": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+            "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+            "engines": {
+                "node": ">= 4.0.0"
+            }
+        },
+        "node_modules/uri-js": {
+            "version": "4.4.1",
+            "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+            "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "punycode": "^2.1.0"
+            }
+        },
+        "node_modules/url-parse": {
+            "version": "1.5.10",
+            "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+            "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+            "dependencies": {
+                "querystringify": "^2.1.1",
+                "requires-port": "^1.0.0"
+            }
+        },
+        "node_modules/v8-compile-cache-lib": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+            "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+            "dev": true
+        },
+        "node_modules/w3c-xmlserializer": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+            "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+            "dependencies": {
+                "xml-name-validator": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
+        "node_modules/webidl-conversions": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+            "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/whatwg-encoding": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+            "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+            "dependencies": {
+                "iconv-lite": "0.6.3"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/whatwg-mimetype": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+            "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/whatwg-url": {
+            "version": "11.0.0",
+            "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+            "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+            "dependencies": {
+                "tr46": "^3.0.0",
+                "webidl-conversions": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/which": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+            "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+            "dev": true,
+            "peer": true,
+            "dependencies": {
+                "isexe": "^2.0.0"
+            },
+            "bin": {
+                "node-which": "bin/node-which"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/which-boxed-primitive": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+            "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+            "dev": true,
+            "dependencies": {
+                "is-bigint": "^1.0.1",
+                "is-boolean-object": "^1.1.0",
+                "is-number-object": "^1.0.4",
+                "is-string": "^1.0.5",
+                "is-symbol": "^1.0.3"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/which-typed-array": {
+            "version": "1.1.9",
+            "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
+            "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+            "dev": true,
+            "dependencies": {
+                "available-typed-arrays": "^1.0.5",
+                "call-bind": "^1.0.2",
+                "for-each": "^0.3.3",
+                "gopd": "^1.0.1",
+                "has-tostringtag": "^1.0.0",
+                "is-typed-array": "^1.1.10"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/word-wrap": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+            "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/wrappy": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+            "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+            "dev": true,
+            "peer": true
+        },
+        "node_modules/ws": {
+            "version": "8.11.0",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
+            "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
+            "engines": {
+                "node": ">=10.0.0"
+            },
+            "peerDependencies": {
+                "bufferutil": "^4.0.1",
+                "utf-8-validate": "^5.0.2"
+            },
+            "peerDependenciesMeta": {
+                "bufferutil": {
+                    "optional": true
+                },
+                "utf-8-validate": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/xml-name-validator": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+            "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/xmlchars": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+            "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
+        },
+        "node_modules/yallist": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+            "dev": true
+        },
+        "node_modules/yn": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+            "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/yocto-queue": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+            "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+            "dev": true,
+            "peer": true,
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/scripts/token_alignment/package.json b/packages/SystemUI/scripts/token_alignment/package.json
new file mode 100644
index 0000000..2e63668
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/package.json
@@ -0,0 +1,22 @@
+{
+    "dependencies": {
+        "csv-parse": "^5.3.3",
+        "high5": "^1.0.0",
+        "jsdom": "^20.0.3"
+    },
+    "devDependencies": {
+        "@types/jsdom": "^20.0.1",
+        "@types/node": "^18.11.18",
+        "@typescript-eslint/eslint-plugin": "^5.48.0",
+        "eslint-config-prettier": "^8.6.0",
+        "eslint-plugin-import": "^2.26.0",
+        "eslint-plugin-prettier": "^4.2.1",
+        "eslint-plugin-simple-import-sort": "^8.0.0",
+        "ts-node": "^10.9.1",
+        "typescript": "^4.9.4"
+    },
+    "scripts": {
+        "main": "ts-node ./index.ts",
+        "resetRepo": "repo forall -j100  -c 'git restore .'"
+    }
+}
diff --git a/packages/SystemUI/scripts/token_alignment/resources/migrationList.csv b/packages/SystemUI/scripts/token_alignment/resources/migrationList.csv
new file mode 100644
index 0000000..4111bb3
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/resources/migrationList.csv
@@ -0,0 +1,16 @@
+android	material	newDefaultValue	newComment	migrationToken
+colorAccentPrimaryVariant	colorPrimaryContainer			MigTok02
+colorAccentSecondaryVariant	colorSecondaryContainer			MigTok04
+colorAccentTertiary	colorTertiary			MigTok05
+colorAccentTertiaryVariant	colorTertiaryContainer			MigTok06
+colorBackground	colorSurfaceContainer			MigTok07
+colorSurface	colorSurfaceContainer			MigTok08
+colorSurfaceHeader	colorSurfaceContainerHighest			MigTok09
+colorSurfaceHighlight	colorSurfaceBright			MigTok10
+colorSurfaceVariant	colorSurfaceContainerHigh			MigTok11
+textColorOnAccent	colorOnPrimary			MigTok12
+textColorPrimary	colorOnSurface			MigTok13
+textColorPrimaryInverse	colorOnShadeInactive			MigTok14
+textColorSecondary	colorOnSurfaceVariant			MigTok15
+textColorSecondaryInverse	colorOnShadeInactiveVariant			MigTok16
+textColorTertiary	colorOutline			MigTok17
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/resources/whitelist/java.json b/packages/SystemUI/scripts/token_alignment/resources/whitelist/java.json
new file mode 100644
index 0000000..7f55b2d
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/resources/whitelist/java.json
@@ -0,0 +1,30 @@
+[
+    "frameworks/base/core/java/android/app/Notification.java",
+    "packages/apps/Settings/src/com/android/settings/dashboard/profileselector/UserAdapter.java",
+    "packages/apps/Settings/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java",
+    "frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt",
+    "frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt",
+    "packages/apps/WallpaperPicker2/src/com/android/wallpaper/picker/PreviewFragment.java",
+    "packages/apps/WallpaperPicker2/src/com/android/wallpaper/picker/CategorySelectorFragment.java",
+    "packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt",
+    "frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java",
+    "vendor/unbundled_google/packages/NexusLauncher/src/com/google/android/apps/nexuslauncher/customize/WallpaperCarouselView.java",
+    "vendor/unbundled_google/packages/NexusLauncher/src/com/google/android/apps/nexuslauncher/quickstep/TaskOverlayFactoryImpl.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserViewBinder.kt",
+    "frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java",
+    "frameworks/base/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java",
+    "frameworks/base/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java"
+]
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls1.json b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls1.json
new file mode 100644
index 0000000..1e59773
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls1.json
@@ -0,0 +1,184 @@
+[
+    "frameworks/base/core/res/res/color/resolver_profile_tab_selected_bg.xml",
+    "frameworks/base/core/res/res/color-night/resolver_profile_tab_selected_bg.xml",
+    "frameworks/base/core/res/res/drawable/autofill_bottomsheet_background.xml",
+    "frameworks/base/core/res/res/drawable/btn_outlined.xml",
+    "frameworks/base/core/res/res/drawable/btn_tonal.xml",
+    "frameworks/base/core/res/res/drawable/chooser_action_button_bg.xml",
+    "frameworks/base/core/res/res/drawable/chooser_row_layer_list.xml",
+    "frameworks/base/core/res/res/drawable/resolver_outlined_button_bg.xml",
+    "frameworks/base/core/res/res/drawable/resolver_profile_tab_bg.xml",
+    "frameworks/base/core/res/res/drawable/toast_frame.xml",
+    "frameworks/base/core/res/res/drawable/work_widget_mask_view_background.xml",
+    "frameworks/base/core/res/res/layout/app_language_picker_current_locale_item.xml",
+    "frameworks/base/core/res/res/layout/app_language_picker_system_current.xml",
+    "frameworks/base/core/res/res/layout/autofill_save.xml",
+    "frameworks/base/core/res/res/layout/chooser_grid.xml",
+    "frameworks/base/core/res/res/layout/user_switching_dialog.xml",
+    "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml",
+    "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_right_bk.xml",
+    "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/rounded_bk.xml",
+    "frameworks/base/packages/SettingsLib/ActionButtonsPreference/res/drawable/square_bk.xml",
+    "packages/modules/IntentResolver/java/res/drawable/chooser_action_button_bg.xml",
+    "packages/modules/IntentResolver/java/res/drawable/chooser_row_layer_list.xml",
+    "packages/modules/IntentResolver/java/res/drawable/resolver_outlined_button_bg.xml",
+    "packages/modules/IntentResolver/java/res/drawable/resolver_profile_tab_bg.xml",
+    "packages/modules/IntentResolver/java/res/layout/chooser_grid.xml",
+    "frameworks/base/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml",
+    "frameworks/base/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_bg.xml",
+    "frameworks/base/libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg.xml",
+    "frameworks/base/libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg_rtl.xml",
+    "vendor/unbundled_google/packages/SystemUIGoogle/bcsmartspace/res/drawable/bg_smartspace_combination_sub_card.xml",
+    "vendor/unbundled_google/packages/SystemUIGoogle/bcsmartspace/res/layout/smartspace_combination_sub_card.xml",
+    "packages/apps/Launcher3/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml",
+    "packages/apps/Launcher3/res/drawable/rounded_action_button.xml",
+    "packages/apps/Launcher3/res/drawable/work_card.xml",
+    "packages/apps/Nfc/res/color/nfc_icon.xml",
+    "packages/apps/Nfc/res/color-night/nfc_icon.xml",
+    "packages/apps/Launcher3/quickstep/res/drawable/bg_overview_clear_all_button.xml",
+    "packages/apps/Launcher3/quickstep/res/drawable/bg_sandbox_feedback.xml",
+    "packages/apps/Launcher3/quickstep/res/drawable/bg_wellbeing_toast.xml",
+    "packages/apps/Launcher3/quickstep/res/drawable/button_taskbar_edu_bordered.xml",
+    "packages/apps/Launcher3/quickstep/res/drawable/button_taskbar_edu_colored.xml",
+    "packages/apps/Launcher3/quickstep/res/drawable/split_instructions_background.xml",
+    "packages/apps/Launcher3/quickstep/res/drawable/task_menu_item_bg.xml",
+    "packages/apps/Launcher3/quickstep/res/layout/digital_wellbeing_toast.xml",
+    "packages/apps/Launcher3/quickstep/res/layout/split_instructions_view.xml",
+    "packages/apps/Launcher3/quickstep/res/layout/taskbar_edu.xml",
+    "frameworks/base/packages/SettingsLib/res/drawable/broadcast_dialog_btn_bg.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/color/share_target_text.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/all_apps_tab_background_selected.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/all_apps_tabs_background.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/arrow_tip_view_bg.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/button_bg.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/shortcut_halo.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/color-v31/surface.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/color-night-v31/all_apps_tab_background_selected.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/color-night-v31/surface.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/bg_pin_keyboard_snackbar_accept_button.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/bg_search_edu_preferences_button.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/circle_accentprimary_32dp.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/ic_search.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/ic_suggest_icon_background.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/share_target_background.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/sticky_snackbar_accept_btn_background.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/sticky_snackbar_background.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/sticky_snackbar_dismiss_btn_background.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/drawable/tall_card_btn_background.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/layout/section_header.xml",
+    "packages/apps/Settings/res/color/dream_card_color_state_list.xml",
+    "packages/apps/Settings/res/color/dream_card_icon_color_state_list.xml",
+    "packages/apps/Settings/res/color/dream_card_text_color_state_list.xml",
+    "packages/apps/Settings/res/drawable/accessibility_text_reading_preview.xml",
+    "packages/apps/Settings/res/drawable/broadcast_button_outline.xml",
+    "packages/apps/Settings/res/drawable/button_border_selected.xml",
+    "packages/apps/Settings/res/drawable/dream_preview_rounded_bg.xml",
+    "packages/apps/Settings/res/drawable/rounded_bg.xml",
+    "packages/apps/Settings/res/drawable/sim_confirm_dialog_btn_outline.xml",
+    "packages/apps/Settings/res/drawable/user_select_background.xml",
+    "packages/apps/Settings/res/drawable/volume_dialog_button_background_outline.xml",
+    "packages/apps/Settings/res/drawable/volume_dialog_button_background_solid.xml",
+    "packages/apps/Settings/res/layout/dream_preview_button.xml",
+    "packages/apps/Settings/res/layout/qrcode_scanner_fragment.xml",
+    "frameworks/base/packages/SystemUI/res/values-television/styles.xml",
+    "frameworks/base/packages/SystemUI/res/color/media_player_album_bg.xml",
+    "frameworks/base/packages/SystemUI/res/color/media_player_outline_button_bg.xml",
+    "frameworks/base/packages/SystemUI/res/color/media_player_solid_button_bg.xml",
+    "frameworks/base/packages/SystemUI/res-keyguard/color/numpad_key_color_secondary.xml",
+    "frameworks/base/packages/SystemUI/res/color/settingslib_state_on.xml",
+    "frameworks/base/packages/SystemUI/res/color/settingslib_track_off.xml",
+    "frameworks/base/packages/SystemUI/res/color/settingslib_track_on.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/accessibility_floating_tooltip_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/action_chip_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/action_chip_container_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/availability_dot_10dp.xml",
+    "frameworks/base/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_header_bg.xml",
+    "frameworks/base/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_item_selected_bg.xml",
+    "frameworks/base/packages/SystemUI/res-keyguard/drawable/bouncer_user_switcher_popup_bg.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/broadcast_dialog_btn_bg.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/fgs_dot.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/fingerprint_bg.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/ic_avatar_with_badge.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml",
+    "frameworks/base/packages/SystemUI/res-keyguard/drawable/kg_bouncer_secondary_button.xml",
+    "frameworks/base/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/logout_button_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/media_ttt_chip_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/media_ttt_chip_background_receiver.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/media_ttt_undo_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/notif_footer_btn_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/notification_guts_bg.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/notification_material_bg.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/overlay_badge_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/overlay_border.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/overlay_button_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/overlay_cancel.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/people_space_messages_count_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/people_tile_status_scrim.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/people_tile_suppressed_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/qs_media_outline_button.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/qs_media_solid_button.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/rounded_bg_full.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/screenrecord_button_background_solid.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/screenrecord_spinner_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/screenshot_edit_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/volume_background_bottom.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/volume_background_top.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/volume_background_top_rounded.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/volume_row_rounded_background.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/volume_row_seekbar.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/wallet_action_button_bg.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/wallet_app_button_bg.xml",
+    "frameworks/base/packages/SystemUI/res/drawable/wallet_empty_state_bg.xml",
+    "frameworks/base/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml",
+    "frameworks/base/packages/SystemUI/res/layout/chipbar.xml",
+    "frameworks/base/packages/SystemUI/res/layout/chipbar.xml",
+    "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay.xml",
+    "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay.xml",
+    "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml",
+    "frameworks/base/packages/SystemUI/res/layout/clipboard_overlay_legacy.xml",
+    "frameworks/base/packages/SystemUI/res/layout/internet_connectivity_dialog.xml",
+    "frameworks/base/packages/SystemUI/res/layout/notification_snooze.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_space_tile_view.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_large_with_content.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_medium_with_content.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_large.xml",
+    "frameworks/base/packages/SystemUI/res/layout/chipbar.xml",
+    "frameworks/base/packages/SystemUI/res/layout/notification_snooze.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_punctuation_background_medium.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_small.xml",
+    "frameworks/base/packages/SystemUI/res/layout/people_tile_small_horizontal.xml",
+    "frameworks/base/packages/SystemUI/res/layout/screen_share_dialog.xml",
+    "frameworks/base/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml",
+    "frameworks/base/packages/SystemUI/res/layout/user_switcher_fullscreen.xml",
+    "frameworks/base/packages/SystemUI/res/layout/user_switcher_fullscreen.xml",
+    "frameworks/base/packages/SystemUI/res/layout/wallet_empty_state.xml",
+    "frameworks/base/packages/SystemUI/res/layout/wallet_fullscreen.xml",
+    "frameworks/base/packages/SystemUI/res/layout/wallet_fullscreen.xml",
+    "frameworks/base/packages/SystemUI/res/layout/chipbar.xml",
+    "frameworks/base/packages/SystemUI/res/layout/notification_snooze.xml",
+    "vendor/unbundled_google/packages/SystemUIGoogle/res/drawable/columbus_chip_background_raw.xml",
+    "vendor/unbundled_google/packages/SystemUIGoogle/res/drawable/columbus_chip_background_raw.xml",
+    "vendor/unbundled_google/packages/SystemUIGoogle/res/drawable/columbus_dialog_background.xml",
+    "vendor/unbundled_google/packages/SystemUIGoogle/res/layout/columbus_target_request_dialog.xml",
+    "vendor/unbundled_google/packages/SettingsGoogle/res/color/dream_card_suw_color_state_list.xml",
+    "vendor/unbundled_google/packages/SettingsGoogle/res/drawable/dream_item_suw_rounded_bg.xml"
+]
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls2.json b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls2.json
new file mode 100644
index 0000000..20a7c76
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/resources/whitelist/xmls2.json
@@ -0,0 +1,13 @@
+[
+    "vendor/google/nexus_overlay/PixelDocumentsUIGoogleOverlay/res/values-v31/themes.xml",
+    "vendor/google/nexus_overlay/PixelDocumentsUIGoogleOverlay/res/values-night-v31/themes.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/values/colors.xml",
+    "vendor/unbundled_google/packages/NexusLauncher/res/values/styles.xml",
+    "packages/apps/Settings/res/values-night/colors.xml",
+    "packages/apps/Settings/res/values/colors.xml",
+    "packages/apps/Settings/res/values/styles.xml",
+    "frameworks/base/packages/SystemUI/res-keyguard/values/styles.xml",
+    "frameworks/base/packages/SystemUI/res/values/styles.xml",
+    "vendor/unbundled_google/packages/SettingsGoogle/res/values/styles.xml",
+    "vendor/unbundled_google/packages/SettingsGoogle/res/values/styles.xml"
+]
\ No newline at end of file
diff --git a/packages/SystemUI/scripts/token_alignment/tsconfig.json b/packages/SystemUI/scripts/token_alignment/tsconfig.json
new file mode 100644
index 0000000..20c7321
--- /dev/null
+++ b/packages/SystemUI/scripts/token_alignment/tsconfig.json
@@ -0,0 +1,103 @@
+{
+  "compilerOptions": {
+    /* Visit https://aka.ms/tsconfig to read more about this file */
+
+    /* Projects */
+    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
+    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
+    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
+    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
+    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
+
+    /* Language and Environment */
+    "target": "ESNext",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
+    // "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */
+    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
+    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+    // "jsxFragmentFactory": "",                         /* Speciffy the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
+    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
+    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
+
+    /* Modules */
+    "module": "commonjs",                                /* Specify what module code is generated. */
+    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
+    // "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
+    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
+    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
+    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
+    "typeRoots": ["../node_modules/@types"],                                  /* Specify multiple folders that act like './node_modules/@types'. */
+    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
+    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
+    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
+    "resolveJsonModule": true,                        /* Enable importing .json files. */
+    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
+
+    /* JavaScript Support */
+    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
+    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+    /* Emit */
+    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
+    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
+    "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
+    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
+    // "removeComments": true,                           /* Disable emitting comments. */
+    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
+    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
+    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
+    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
+    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
+    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
+    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
+    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
+    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
+    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
+    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
+    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+    /* Interop Constraints */
+    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
+    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
+    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
+
+    /* Type Checking */
+    "strict": true,                                      /* Enable all strict type-checking options. */
+    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
+    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
+    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
+    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
+    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
+    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
+    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
+    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
+    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
+    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
+    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
+    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
+    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
+    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
+    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
+
+    /* Completeness */
+    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
+    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
+  }
+}
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index b679cfa1..b7d9df9 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -85,6 +85,9 @@
     static_libs: [
         "SystemUI-flag-types",
     ],
+    optimize: {
+        proguard_flags_files: ["proguard_flags.flags"],
+    },
     java_version: "1.8",
     min_sdk_version: "current",
 }
diff --git a/packages/SystemUI/shared/proguard_flags.flags b/packages/SystemUI/shared/proguard_flags.flags
new file mode 100644
index 0000000..08859cd
--- /dev/null
+++ b/packages/SystemUI/shared/proguard_flags.flags
@@ -0,0 +1 @@
+-keep class * implements com.android.systemui.flags.ParcelableFlag
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index 196f7f0..c9a25b0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -47,10 +47,6 @@
     val resourceId: Int
 }
 
-interface DeviceConfigFlag<T> : Flag<T> {
-    val default: T
-}
-
 interface SysPropFlag<T> : Flag<T> {
     val default: T
 }
@@ -80,8 +76,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readBoolean(),
         teamfood = parcel.readBoolean(),
         overridden = parcel.readBoolean()
@@ -137,21 +133,6 @@
 ) : ResourceFlag<Boolean>
 
 /**
- * A Flag that can reads its overrides from DeviceConfig.
- *
- * This is generally useful for flags that come from or are used _outside_ of SystemUI.
- *
- * Prefer [UnreleasedFlag] and [ReleasedFlag].
- */
-data class DeviceConfigBooleanFlag constructor(
-    override val id: Int,
-    override val name: String,
-    override val namespace: String,
-    override val default: Boolean = false,
-    override val teamfood: Boolean = false
-) : DeviceConfigFlag<Boolean>
-
-/**
  * A Flag that can reads its overrides from System Properties.
  *
  * This is generally useful for flags that come from or are used _outside_ of SystemUI.
@@ -186,8 +167,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readString() ?: ""
     )
 
@@ -226,8 +207,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readInt()
     )
 
@@ -266,8 +247,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readLong()
     )
 
@@ -298,8 +279,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readFloat()
     )
 
@@ -338,8 +319,8 @@
 
     private constructor(parcel: Parcel) : this(
         id = parcel.readInt(),
-        name = parcel.readString(),
-        namespace = parcel.readString(),
+        name = parcel.readString() ?: "",
+        namespace = parcel.readString() ?: "",
         default = parcel.readDouble()
     )
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
index 195ba465..72a4fab 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
@@ -34,7 +34,7 @@
     /** An event representing the change */
     interface FlagEvent {
         /** the id of the flag which changed */
-        val flagId: Int
+        val flagName: String
         /** if all listeners alerted invoke this method, the restart will be skipped */
         fun requestNoRestart()
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index d85292a..da1641c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -39,7 +39,7 @@
         const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
         const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
         const val ACTION_SYSUI_STARTED = "com.android.systemui.STARTED"
-        const val EXTRA_ID = "id"
+        const val EXTRA_NAME = "name"
         const val EXTRA_VALUE = "value"
         const val EXTRA_FLAGS = "flags"
         private const val SETTINGS_PREFIX = "systemui/flags"
@@ -56,7 +56,7 @@
      * that the restart be suppressed
      */
     var onSettingsChangedAction: Consumer<Boolean>? = null
-    var clearCacheAction: Consumer<Int>? = null
+    var clearCacheAction: Consumer<String>? = null
     private val listeners: MutableSet<PerFlagListener> = mutableSetOf()
     private val settingsObserver: ContentObserver = SettingsObserver()
 
@@ -96,35 +96,42 @@
      * Returns the stored value or null if not set.
      * This API is used by TheFlippinApp.
      */
-    fun isEnabled(id: Int): Boolean? = readFlagValue(id, BooleanFlagSerializer)
+    fun isEnabled(name: String): Boolean? = readFlagValue(name, BooleanFlagSerializer)
 
     /**
      * Sets the value of a boolean flag.
      * This API is used by TheFlippinApp.
      */
-    fun setFlagValue(id: Int, enabled: Boolean) {
-        val intent = createIntent(id)
+    fun setFlagValue(name: String, enabled: Boolean) {
+        val intent = createIntent(name)
         intent.putExtra(EXTRA_VALUE, enabled)
 
         context.sendBroadcast(intent)
     }
 
-    fun eraseFlag(id: Int) {
-        val intent = createIntent(id)
+    fun eraseFlag(name: String) {
+        val intent = createIntent(name)
 
         context.sendBroadcast(intent)
     }
 
     /** Returns the stored value or null if not set.  */
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     fun <T> readFlagValue(id: Int, serializer: FlagSerializer<T>): T? {
-        val data = settings.getString(idToSettingsKey(id))
+        val data = settings.getStringFromSecure(idToSettingsKey(id))
+        return serializer.fromSettingsData(data)
+    }
+
+    /** Returns the stored value or null if not set.  */
+    fun <T> readFlagValue(name: String, serializer: FlagSerializer<T>): T? {
+        val data = settings.getString(nameToSettingsKey(name))
         return serializer.fromSettingsData(data)
     }
 
     override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
         synchronized(listeners) {
             val registerNeeded = listeners.isEmpty()
-            listeners.add(PerFlagListener(flag.id, listener))
+            listeners.add(PerFlagListener(flag.name, listener))
             if (registerNeeded) {
                 settings.registerContentObserver(SETTINGS_PREFIX, true, settingsObserver)
             }
@@ -143,38 +150,38 @@
         }
     }
 
-    private fun createIntent(id: Int): Intent {
+    private fun createIntent(name: String): Intent {
         val intent = Intent(ACTION_SET_FLAG)
         intent.setPackage(RECEIVING_PACKAGE)
-        intent.putExtra(EXTRA_ID, id)
+        intent.putExtra(EXTRA_NAME, name)
 
         return intent
     }
 
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     fun idToSettingsKey(id: Int): String {
         return "$SETTINGS_PREFIX/$id"
     }
 
+    fun nameToSettingsKey(name: String): String {
+        return "$SETTINGS_PREFIX/$name"
+    }
+
     inner class SettingsObserver : ContentObserver(handler) {
         override fun onChange(selfChange: Boolean, uri: Uri?) {
             if (uri == null) {
                 return
             }
             val parts = uri.pathSegments
-            val idStr = parts[parts.size - 1]
-            val id = try {
-                idStr.toInt()
-            } catch (e: NumberFormatException) {
-                return
-            }
-            clearCacheAction?.accept(id)
-            dispatchListenersAndMaybeRestart(id, onSettingsChangedAction)
+            val name = parts[parts.size - 1]
+            clearCacheAction?.accept(name)
+            dispatchListenersAndMaybeRestart(name, onSettingsChangedAction)
         }
     }
 
-    fun dispatchListenersAndMaybeRestart(id: Int, restartAction: Consumer<Boolean>?) {
+    fun dispatchListenersAndMaybeRestart(name: String, restartAction: Consumer<Boolean>?) {
         val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) {
-            listeners.mapNotNull { if (it.id == id) it.listener else null }
+            listeners.mapNotNull { if (it.name == name) it.listener else null }
         }
         // If there are no listeners, there's nothing to dispatch to, and nothing to suppress it.
         if (filteredListeners.isEmpty()) {
@@ -185,7 +192,7 @@
         val suppressRestartList: List<Boolean> = filteredListeners.map { listener ->
             var didRequestNoRestart = false
             val event = object : FlagListenable.FlagEvent {
-                override val flagId = id
+                override val flagName = name
                 override fun requestNoRestart() {
                     didRequestNoRestart = true
                 }
@@ -198,7 +205,7 @@
         restartAction?.accept(suppressRestart)
     }
 
-    private data class PerFlagListener(val id: Int, val listener: FlagListenable.Listener)
+    private data class PerFlagListener(val name: String, val listener: FlagListenable.Listener)
 }
 
 class NoFlagResultsException : Exception(
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
index 742bb0b..6beb851 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
@@ -22,7 +22,10 @@
 
 class FlagSettingsHelper(private val contentResolver: ContentResolver) {
 
-    fun getString(key: String): String? = Settings.Secure.getString(contentResolver, key)
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
+    fun getStringFromSecure(key: String): String? = Settings.Secure.getString(contentResolver, key)
+
+    fun getString(key: String): String? = Settings.Global.getString(contentResolver, key)
 
     fun registerContentObserver(
         name: String,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt
new file mode 100644
index 0000000..a9a5cf9
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.systemui.shared.hardware
+
+import android.view.InputDevice
+
+/**
+ * Returns true if [InputDevice] is electronic components to allow a user to use an active stylus in
+ * the host device or a passive stylus is detected by the host device.
+ */
+val InputDevice.isInternalStylusSource: Boolean
+    get() = isAnyStylusSource && !isExternal
+
+/** Returns true if [InputDevice] is an active stylus. */
+val InputDevice.isExternalStylusSource: Boolean
+    get() = isAnyStylusSource && isExternal
+
+/**
+ * Returns true if [InputDevice] supports any stylus source.
+ *
+ * @see InputDevice.isInternalStylusSource
+ * @see InputDevice.isExternalStylusSource
+ */
+val InputDevice.isAnyStylusSource: Boolean
+    get() = supportsSource(InputDevice.SOURCE_STYLUS)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt
new file mode 100644
index 0000000..f020b4e
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.systemui.shared.hardware
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+
+/**
+ * Gets information about all input devices in the system and returns as a lazy [Sequence].
+ *
+ * For performance reasons, it is preferred to operate atop the returned [Sequence] to ensure each
+ * operation is executed on an element-per-element basis yet customizable.
+ *
+ * For example:
+ * ```kotlin
+ * val stylusDevices = inputManager.getInputDeviceSequence().filter {
+ *   it.supportsSource(InputDevice.SOURCE_STYLUS)
+ * }
+ *
+ * val hasInternalStylus = stylusDevices.any { it.isInternal }
+ * val hasExternalStylus = stylusDevices.any { !it.isInternal }
+ * ```
+ *
+ * @return a [Sequence] of [InputDevice].
+ */
+fun InputManager.getInputDeviceSequence(): Sequence<InputDevice> =
+    inputDeviceIds.asSequence().mapNotNull { getInputDevice(it) }
+
+/**
+ * Returns the first [InputDevice] matching the given predicate, or null if no such [InputDevice]
+ * was found.
+ */
+fun InputManager.findInputDevice(predicate: (InputDevice) -> Boolean): InputDevice? =
+    getInputDeviceSequence().find { predicate(it) }
+
+/**
+ * Returns true if [any] [InputDevice] matches with [predicate].
+ *
+ * For example:
+ * ```kotlin
+ * val hasStylusSupport = inputManager.hasInputDevice { it.isStylusSupport() }
+ * val hasStylusPen = inputManager.hasInputDevice { it.isStylusPen() }
+ * ```
+ */
+fun InputManager.hasInputDevice(predicate: (InputDevice) -> Boolean): Boolean =
+    getInputDeviceSequence().any { predicate(it) }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isInternalStylusSource]. */
+fun InputManager.hasInternalStylusSource(): Boolean = hasInputDevice { it.isInternalStylusSource }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isExternalStylusSource]. */
+fun InputManager.hasExternalStylusSource(): Boolean = hasInputDevice { it.isExternalStylusSource }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isAnyStylusSource]. */
+fun InputManager.hasAnyStylusSource(): Boolean = hasInputDevice { it.isAnyStylusSource }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 1c532fe..b8bddd1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.view.MotionEvent;
+import com.android.internal.util.ScreenshotRequest;
 
 import com.android.systemui.shared.recents.model.Task;
 
@@ -87,12 +88,6 @@
     void notifyPrioritizedRotation(int rotation) = 25;
 
     /**
-     * Handle the provided image as if it was a screenshot.
-     */
-    void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen,
-              in Insets visibleInsets, in Task.TaskKey task) = 28;
-
-    /**
      * Notifies to expand notification panel.
      */
     void expandNotificationPanel() = 29;
@@ -125,5 +120,10 @@
      */
     void toggleNotificationPanel() = 50;
 
-    // Next id = 51
+    /**
+     * Handle the screenshot request.
+     */
+    void takeScreenshot(in ScreenshotRequest request) = 51;
+
+    // Next id = 52
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 0ee813b..ef2247f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -15,39 +15,36 @@
  */
 package com.android.systemui.shared.regionsampling
 
+import android.app.WallpaperColors
+import android.app.WallpaperManager
 import android.graphics.Color
+import android.graphics.Point
 import android.graphics.Rect
+import android.graphics.RectF
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCallback
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 
 /** Class for instance of RegionSamplingHelper */
-open class RegionSampler(
-    sampledView: View?,
+open class RegionSampler
+@JvmOverloads
+constructor(
+    val sampledView: View?,
     mainExecutor: Executor?,
-    bgExecutor: Executor?,
-    regionSamplingEnabled: Boolean,
-    updateFun: UpdateColorCallback
-) {
+    val bgExecutor: Executor?,
+    val regionSamplingEnabled: Boolean,
+    val updateForegroundColor: UpdateColorCallback,
+    val wallpaperManager: WallpaperManager? = WallpaperManager.getInstance(sampledView?.context)
+) : WallpaperManager.LocalWallpaperColorConsumer {
     private var regionDarkness = RegionDarkness.DEFAULT
     private var samplingBounds = Rect()
     private val tmpScreenLocation = IntArray(2)
     @VisibleForTesting var regionSampler: RegionSamplingHelper? = null
     private var lightForegroundColor = Color.WHITE
     private var darkForegroundColor = Color.BLACK
-
-    @VisibleForTesting
-    open fun createRegionSamplingHelper(
-        sampledView: View,
-        callback: SamplingCallback,
-        mainExecutor: Executor?,
-        bgExecutor: Executor?
-    ): RegionSamplingHelper {
-        return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
-    }
+    private val displaySize = Point()
 
     /**
      * Sets the colors to be used for Dark and Light Foreground.
@@ -73,7 +70,7 @@
         }
     }
 
-    private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness {
+    private fun getRegionDarkness(isRegionDark: Boolean): RegionDarkness {
         return if (isRegionDark) {
             RegionDarkness.DARK
         } else {
@@ -87,12 +84,32 @@
 
     /** Start region sampler */
     fun startRegionSampler() {
-        regionSampler?.start(samplingBounds)
+        if (!regionSamplingEnabled || sampledView == null) {
+            return
+        }
+
+        val sampledRegion = calculateSampledRegion(sampledView)
+        val regions = ArrayList<RectF>()
+        val sampledRegionWithOffset = convertBounds(sampledRegion)
+        regions.add(sampledRegionWithOffset)
+
+        wallpaperManager?.removeOnColorsChangedListener(this)
+        wallpaperManager?.addOnColorsChangedListener(this, regions)
+
+        // TODO(b/265969235): conditionally set FLAG_LOCK or FLAG_SYSTEM once HS smartspace
+        // implemented
+        bgExecutor?.execute(
+            Runnable {
+                val initialSampling =
+                    wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)
+                onColorsChanged(sampledRegionWithOffset, initialSampling)
+            }
+        )
     }
 
     /** Stop region sampler */
     fun stopRegionSampler() {
-        regionSampler?.stop()
+        wallpaperManager?.removeOnColorsChangedListener(this)
     }
 
     /** Dump region sampler */
@@ -100,43 +117,66 @@
         regionSampler?.dump(pw)
     }
 
-    init {
-        if (regionSamplingEnabled && sampledView != null) {
-            regionSampler =
-                createRegionSamplingHelper(
-                    sampledView,
-                    object : SamplingCallback {
-                        override fun onRegionDarknessChanged(isRegionDark: Boolean) {
-                            regionDarkness = convertToClockDarkness(isRegionDark)
-                            updateFun()
-                        }
-                        /**
-                         * The method getLocationOnScreen is used to obtain the view coordinates
-                         * relative to its left and top edges on the device screen. Directly
-                         * accessing the X and Y coordinates of the view returns the location
-                         * relative to its parent view instead.
-                         */
-                        override fun getSampledRegion(sampledView: View): Rect {
-                            val screenLocation = tmpScreenLocation
-                            sampledView.getLocationOnScreen(screenLocation)
-                            val left = screenLocation[0]
-                            val top = screenLocation[1]
-                            samplingBounds.left = left
-                            samplingBounds.top = top
-                            samplingBounds.right = left + sampledView.width
-                            samplingBounds.bottom = top + sampledView.height
-                            return samplingBounds
-                        }
+    fun calculateSampledRegion(sampledView: View): RectF {
+        val screenLocation = tmpScreenLocation
+        /**
+         * The method getLocationOnScreen is used to obtain the view coordinates relative to its
+         * left and top edges on the device screen. Directly accessing the X and Y coordinates of
+         * the view returns the location relative to its parent view instead.
+         */
+        sampledView.getLocationOnScreen(screenLocation)
+        val left = screenLocation[0]
+        val top = screenLocation[1]
 
-                        override fun isSamplingEnabled(): Boolean {
-                            return regionSamplingEnabled
-                        }
-                    },
-                    mainExecutor,
-                    bgExecutor
-                )
-        }
-        regionSampler?.setWindowVisible(true)
+        samplingBounds.left = left
+        samplingBounds.top = top
+        samplingBounds.right = left + sampledView.width
+        samplingBounds.bottom = top + sampledView.height
+
+        return RectF(samplingBounds)
+    }
+
+    /**
+     * Convert the bounds of the region we want to sample from to fractional offsets because
+     * WallpaperManager requires the bounds to be between [0,1]. The wallpaper is treated as one
+     * continuous image, so if there are multiple screens, then each screen falls into a fractional
+     * range. For instance, 4 screens have the ranges [0, 0.25], [0,25, 0.5], [0.5, 0.75], [0.75,
+     * 1].
+     */
+    fun convertBounds(originalBounds: RectF): RectF {
+
+        // TODO(b/265969235): GRAB # PAGES + CURRENT WALLPAPER PAGE # FROM LAUNCHER
+        // TODO(b/265968912): remove hard-coded value once LS wallpaper supported
+        val wallpaperPageNum = 0
+        val numScreens = 1
+
+        val screenWidth = displaySize.x
+        // TODO: investigate small difference between this and the height reported in go/web-hv
+        val screenHeight = displaySize.y
+
+        val newBounds = RectF()
+        // horizontal
+        newBounds.left = ((originalBounds.left / screenWidth) + wallpaperPageNum) / numScreens
+        newBounds.right = ((originalBounds.right / screenWidth) + wallpaperPageNum) / numScreens
+        // vertical
+        newBounds.top = originalBounds.top / screenHeight
+        newBounds.bottom = originalBounds.bottom / screenHeight
+
+        return newBounds
+    }
+
+    init {
+        sampledView?.context?.display?.getSize(displaySize)
+    }
+
+    override fun onColorsChanged(area: RectF?, colors: WallpaperColors?) {
+        // update text color when wallpaper color changes
+        regionDarkness =
+            getRegionDarkness(
+                (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) !=
+                    WallpaperColors.HINT_SUPPORTS_DARK_TEXT
+            )
+        updateForegroundColor()
     }
 }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
index 857cc462..5d036fb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -35,6 +35,7 @@
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
 
+import androidx.annotation.BoolRes;
 import androidx.core.view.OneShotPreDrawListener;
 
 import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
@@ -65,6 +66,8 @@
     private final int mTaskbarBottomMarginResource;
     @DimenRes
     private final int mButtonDiameterResource;
+    @BoolRes
+    private final int mFloatingRotationBtnPositionLeftResource;
 
     private AnimatedVectorDrawable mAnimatedDrawable;
     private boolean mIsShowing;
@@ -84,7 +87,7 @@
             @LayoutRes int layout, @IdRes int keyButtonId, @DimenRes int minMargin,
             @DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin,
             @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter,
-            @DimenRes int rippleMaxWidth) {
+            @DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) {
         mWindowManager = context.getSystemService(WindowManager.class);
         mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(layout, null);
         mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId);
@@ -100,6 +103,7 @@
         mTaskbarLeftMarginResource = taskbarLeftMargin;
         mTaskbarBottomMarginResource = taskbarBottomMargin;
         mButtonDiameterResource = buttonDiameter;
+        mFloatingRotationBtnPositionLeftResource = floatingRotationBtnPositionLeftResource;
 
         updateDimensionResources();
     }
@@ -116,8 +120,11 @@
         int taskbarMarginBottom =
                 res.getDimensionPixelSize(mTaskbarBottomMarginResource);
 
+        boolean floatingRotationButtonPositionLeft =
+                res.getBoolean(mFloatingRotationBtnPositionLeftResource);
+
         mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin,
-                taskbarMarginLeft, taskbarMarginBottom);
+                taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft);
 
         final int diameter = res.getDimensionPixelSize(mButtonDiameterResource);
         mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
index ec3c073..40e43a9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
@@ -10,7 +10,8 @@
 class FloatingRotationButtonPositionCalculator(
     private val defaultMargin: Int,
     private val taskbarMarginLeft: Int,
-    private val taskbarMarginBottom: Int
+    private val taskbarMarginBottom: Int,
+    private val floatingRotationButtonPositionLeft: Boolean
 ) {
 
     fun calculatePosition(
@@ -18,7 +19,6 @@
         taskbarVisible: Boolean,
         taskbarStashed: Boolean
     ): Position {
-
         val isTaskbarSide = currentRotation == Surface.ROTATION_0
             || currentRotation == Surface.ROTATION_90
         val useTaskbarMargin = isTaskbarSide && taskbarVisible && !taskbarStashed
@@ -55,11 +55,21 @@
     )
 
     private fun resolveGravity(rotation: Int): Int =
-        when (rotation) {
-            Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT
-            Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT
-            Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT
-            Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT
-            else -> throw IllegalArgumentException("Invalid rotation $rotation")
+        if (floatingRotationButtonPositionLeft) {
+            when (rotation) {
+                Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.LEFT
+                Surface.ROTATION_90 -> Gravity.BOTTOM or Gravity.RIGHT
+                Surface.ROTATION_180 -> Gravity.TOP or Gravity.RIGHT
+                Surface.ROTATION_270 -> Gravity.TOP or Gravity.LEFT
+                else -> throw IllegalArgumentException("Invalid rotation $rotation")
+            }
+        } else {
+            when (rotation) {
+                Surface.ROTATION_0 -> Gravity.BOTTOM or Gravity.RIGHT
+                Surface.ROTATION_90 -> Gravity.TOP or Gravity.RIGHT
+                Surface.ROTATION_180 -> Gravity.TOP or Gravity.LEFT
+                Surface.ROTATION_270 -> Gravity.BOTTOM or Gravity.LEFT
+                else -> throw IllegalArgumentException("Invalid rotation $rotation")
+            }
         }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 766266d..037a71e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -112,7 +112,8 @@
     public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25;
     // Freeform windows are showing in desktop mode
     public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26;
-
+    // Device dreaming state
+    public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -141,7 +142,8 @@
             SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED,
             SYSUI_STATE_IMMERSIVE_MODE,
             SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING,
-            SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE
+            SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE,
+            SYSUI_STATE_DEVICE_DREAMING
     })
     public @interface SystemUiStateFlags {}
 
@@ -179,6 +181,7 @@
         str.add((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 ? "vis_win_showing" : "");
         str.add((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0
                 ? "freeform_active_in_desktop_mode" : "");
+        str.add((flags & SYSUI_STATE_DEVICE_DREAMING) != 0 ? "device_dreaming" : "");
         return str.toString();
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
index 7f2933e..c9e57b4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
@@ -15,21 +15,51 @@
 package com.android.systemui.unfold.system
 
 import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration
+import android.os.Trace
+import com.android.systemui.shared.system.TaskStackChangeListener
+import com.android.systemui.shared.system.TaskStackChangeListeners
 import com.android.systemui.unfold.util.CurrentActivityTypeProvider
 import javax.inject.Inject
 import javax.inject.Singleton
 
 @Singleton
-class ActivityManagerActivityTypeProvider @Inject constructor(
-    private val activityManager: ActivityManager
-) : CurrentActivityTypeProvider {
+class ActivityManagerActivityTypeProvider
+@Inject
+constructor(private val activityManager: ActivityManager) : CurrentActivityTypeProvider {
 
     override val isHomeActivity: Boolean?
-        get() {
-            val activityType = activityManager.getRunningTasks(/* maxNum= */ 1)
-                    ?.getOrNull(0)?.topActivityType ?: return null
+        get() = _isHomeActivity
 
-            return activityType == WindowConfiguration.ACTIVITY_TYPE_HOME
+    private var _isHomeActivity: Boolean? = null
+
+
+    override fun init() {
+        _isHomeActivity = activityManager.isOnHomeActivity()
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
+    }
+
+    override fun uninit() {
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
+    }
+
+    private val taskStackChangeListener =
+        object : TaskStackChangeListener {
+            override fun onTaskMovedToFront(taskInfo: RunningTaskInfo) {
+                _isHomeActivity = taskInfo.isHomeActivity()
+            }
         }
+
+    private fun RunningTaskInfo.isHomeActivity(): Boolean =
+        topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME
+
+    private fun ActivityManager.isOnHomeActivity(): Boolean? {
+        try {
+            Trace.beginSection("isOnHomeActivity")
+            return getRunningTasks(/* maxNum= */ 1)?.firstOrNull()?.isHomeActivity()
+        } finally {
+            Trace.endSection()
+        }
+    }
 }
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 05372fe..31234cf 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -35,7 +35,7 @@
         teamfood: Boolean = false
     ): UnreleasedFlag {
         val flag = UnreleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
-        FlagsFactory.checkForDupesAndAdd(flag)
+        checkForDupesAndAdd(flag)
         return flag
     }
 
@@ -46,7 +46,7 @@
         teamfood: Boolean = false
     ): ReleasedFlag {
         val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
-        FlagsFactory.checkForDupesAndAdd(flag)
+        checkForDupesAndAdd(flag)
         return flag
     }
 
@@ -65,7 +65,7 @@
                 resourceId = resourceId,
                 teamfood = teamfood
             )
-        FlagsFactory.checkForDupesAndAdd(flag)
+        checkForDupesAndAdd(flag)
         return flag
     }
 
@@ -77,18 +77,13 @@
     ): SysPropBooleanFlag {
         val flag =
             SysPropBooleanFlag(id = id, name = name, namespace = "systemui", default = default)
-        FlagsFactory.checkForDupesAndAdd(flag)
+        checkForDupesAndAdd(flag)
         return flag
     }
 
     private fun checkForDupesAndAdd(flag: Flag<*>) {
         if (flagMap.containsKey(flag.name)) {
-            throw IllegalArgumentException("Name {flag.name} is already registered")
-        }
-        flagMap.forEach {
-            if (it.value.id == flag.id) {
-                throw IllegalArgumentException("Name {flag.id} is already registered")
-            }
+            throw IllegalArgumentException("Name {$flag.name} is already registered")
         }
         flagMap[flag.name] = flag
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index a45ce42..ea079a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -24,6 +24,8 @@
 import android.text.format.DateFormat
 import android.util.TypedValue
 import android.view.View
+import android.widget.FrameLayout
+import android.view.ViewTreeObserver
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -41,24 +43,26 @@
 import com.android.systemui.log.dagger.KeyguardSmallClockLog
 import com.android.systemui.log.dagger.KeyguardLargeClockLog
 import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.ClockTickRate
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.shared.regionsampling.RegionSampler
+import com.android.systemui.statusbar.Weather
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
 import com.android.systemui.statusbar.policy.ConfigurationController
-import java.io.PrintWriter
+import com.android.systemui.util.concurrency.DelayableExecutor
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
 import java.util.Locale
 import java.util.TimeZone
 import java.util.concurrent.Executor
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.launch
 
 /**
  * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -73,7 +77,7 @@
     private val configurationController: ConfigurationController,
     @Main private val resources: Resources,
     private val context: Context,
-    @Main private val mainExecutor: Executor,
+    @Main private val mainExecutor: DelayableExecutor,
     @Background private val bgExecutor: Executor,
     @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
     @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
@@ -89,8 +93,15 @@
                 value.largeClock.logBuffer = largeLogBuffer
 
                 value.initialize(resources, dozeAmount, 0f)
-                updateRegionSamplers(value)
+
+                if (regionSamplingEnabled) {
+                    clock?.smallClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
+                    clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
+                } else {
+                    updateColors()
+                }
                 updateFontSizes()
+                updateTimeListeners()
             }
         }
 
@@ -104,47 +115,87 @@
     private var disposableHandle: DisposableHandle? = null
     private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
 
-    private fun updateColors() {
+    private val mLayoutChangedListener = object : View.OnLayoutChangeListener {
+        private var currentSmallClockView: View? = null
+        private var currentLargeClockView: View? = null
+        private var currentSmallClockLocation = IntArray(2)
+        private var currentLargeClockLocation = IntArray(2)
 
-        if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
-            val wallpaperManager = WallpaperManager.getInstance(context)
-            if (!wallpaperManager.lockScreenWallpaperExists()) {
-                smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark
-                largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark
-            }
-        } else {
-            val isLightTheme = TypedValue()
-            context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
-            smallClockIsDark = isLightTheme.data == 0
-            largeClockIsDark = isLightTheme.data == 0
+        override fun onLayoutChange(
+            view: View?,
+            left: Int,
+            top: Int,
+            right: Int,
+            bottom: Int,
+            oldLeft: Int,
+            oldTop: Int,
+            oldRight: Int,
+            oldBottom: Int
+        ) {
+        val parent = (view?.parent) as FrameLayout
+
+        // don't pass in negative bounds when clocks are in transition state
+        if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) {
+            return
         }
 
+        // SMALL CLOCK
+        if (parent.id == R.id.lockscreen_clock_view) {
+            // view bounds have changed due to clock size changing (i.e. different character widths)
+            // AND/OR the view has been translated when transitioning between small and large clock
+            if (view != currentSmallClockView ||
+                !view.locationOnScreen.contentEquals(currentSmallClockLocation)) {
+                currentSmallClockView = view
+                currentSmallClockLocation = view.locationOnScreen
+                updateRegionSampler(view)
+            }
+        }
+        // LARGE CLOCK
+        else if (parent.id == R.id.lockscreen_clock_view_large) {
+            if (view != currentLargeClockView ||
+                !view.locationOnScreen.contentEquals(currentLargeClockLocation)) {
+                currentLargeClockView = view
+                currentLargeClockLocation = view.locationOnScreen
+                updateRegionSampler(view)
+            }
+        }
+        }
+    }
+
+    private fun updateColors() {
+        val wallpaperManager = WallpaperManager.getInstance(context)
+        if (regionSamplingEnabled && !wallpaperManager.lockScreenWallpaperExists()) {
+            if (regionSampler != null) {
+                if (regionSampler?.sampledView == clock?.smallClock?.view) {
+                    smallClockIsDark = regionSampler!!.currentRegionDarkness().isDark
+                    clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark)
+                    return
+                } else if (regionSampler?.sampledView == clock?.largeClock?.view) {
+                    largeClockIsDark = regionSampler!!.currentRegionDarkness().isDark
+                    clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark)
+                    return
+                }
+            }
+        }
+
+        val isLightTheme = TypedValue()
+        context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
+        smallClockIsDark = isLightTheme.data == 0
+        largeClockIsDark = isLightTheme.data == 0
+
         clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark)
         clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark)
     }
 
-    private fun updateRegionSamplers(currentClock: ClockController?) {
-        smallRegionSampler?.stopRegionSampler()
-        largeRegionSampler?.stopRegionSampler()
-
-        smallRegionSampler = createRegionSampler(
-                currentClock?.smallClock?.view,
-                mainExecutor,
-                bgExecutor,
-                regionSamplingEnabled,
-                ::updateColors
-        )
-
-        largeRegionSampler = createRegionSampler(
-                currentClock?.largeClock?.view,
-                mainExecutor,
-                bgExecutor,
-                regionSamplingEnabled,
-                ::updateColors
-        )
-
-        smallRegionSampler!!.startRegionSampler()
-        largeRegionSampler!!.startRegionSampler()
+    private fun updateRegionSampler(sampledRegion: View) {
+        regionSampler?.stopRegionSampler()
+        regionSampler = createRegionSampler(
+            sampledRegion,
+            mainExecutor,
+            bgExecutor,
+            regionSamplingEnabled,
+            ::updateColors
+        )?.apply { startRegionSampler() }
 
         updateColors()
     }
@@ -155,7 +206,7 @@
             bgExecutor: Executor?,
             regionSamplingEnabled: Boolean,
             updateColors: () -> Unit
-    ): RegionSampler {
+    ): RegionSampler? {
         return RegionSampler(
             sampledView,
             mainExecutor,
@@ -164,8 +215,11 @@
             updateColors)
     }
 
-    var smallRegionSampler: RegionSampler? = null
-    var largeRegionSampler: RegionSampler? = null
+    var regionSampler: RegionSampler? = null
+    var smallTimeListener: TimeListener? = null
+    var largeTimeListener: TimeListener? = null
+    val shouldTimeListenerRun: Boolean
+        get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
 
     private var smallClockIsDark = true
     private var largeClockIsDark = true
@@ -204,6 +258,9 @@
                     clock?.animations?.doze(if (isDozing) 1f else 0f)
                 }
             }
+
+            smallTimeListener?.update(shouldTimeListenerRun)
+            largeTimeListener?.update(shouldTimeListenerRun)
         }
 
         override fun onTimeFormatChanged(timeFormat: String) {
@@ -217,6 +274,12 @@
         override fun onUserSwitchComplete(userId: Int) {
             clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
         }
+
+        override fun onWeatherDataChanged(data: Weather?) {
+            if (data != null) {
+                clock?.events?.onWeatherDataChanged(data)
+            }
+        }
     }
 
     fun registerListeners(parent: View) {
@@ -232,8 +295,6 @@
         configurationController.addCallback(configListener)
         batteryController.addCallback(batteryCallback)
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
-        smallRegionSampler?.startRegionSampler()
-        largeRegionSampler?.startRegionSampler()
         disposableHandle = parent.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 listenForDozing(this)
@@ -245,6 +306,8 @@
                 }
             }
         }
+        smallTimeListener?.update(shouldTimeListenerRun)
+        largeTimeListener?.update(shouldTimeListenerRun)
     }
 
     fun unregisterListeners() {
@@ -258,8 +321,26 @@
         configurationController.removeCallback(configListener)
         batteryController.removeCallback(batteryCallback)
         keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
-        smallRegionSampler?.stopRegionSampler()
-        largeRegionSampler?.stopRegionSampler()
+        regionSampler?.stopRegionSampler()
+        smallTimeListener?.stop()
+        largeTimeListener?.stop()
+    }
+
+    private fun updateTimeListeners() {
+        smallTimeListener?.stop()
+        largeTimeListener?.stop()
+
+        smallTimeListener = null
+        largeTimeListener = null
+
+        clock?.smallClock?.let {
+            smallTimeListener = TimeListener(it, mainExecutor)
+            smallTimeListener?.update(shouldTimeListenerRun)
+        }
+        clock?.largeClock?.let {
+            largeTimeListener = TimeListener(it, mainExecutor)
+            largeTimeListener?.update(shouldTimeListenerRun)
+        }
     }
 
     private fun updateFontSizes() {
@@ -269,22 +350,18 @@
             resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
     }
 
-    /**
-     * Dump information for debugging
-     */
-    fun dump(pw: PrintWriter) {
-        pw.println(this)
-        clock?.dump(pw)
-        smallRegionSampler?.dump(pw)
-        largeRegionSampler?.dump(pw)
+    private fun handleDoze(doze: Float) {
+        dozeAmount = doze
+        clock?.animations?.doze(dozeAmount)
+        smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
+        largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
     }
 
     @VisibleForTesting
     internal fun listenForDozeAmount(scope: CoroutineScope): Job {
         return scope.launch {
             keyguardInteractor.dozeAmount.collect {
-                dozeAmount = it
-                clock?.animations?.doze(dozeAmount)
+                handleDoze(it)
             }
         }
     }
@@ -293,8 +370,7 @@
     internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
         return scope.launch {
             keyguardTransitionInteractor.dozeAmountTransition.collect {
-                dozeAmount = it.value
-                clock?.animations?.doze(dozeAmount)
+                handleDoze(it.value)
             }
         }
     }
@@ -309,8 +385,7 @@
             keyguardTransitionInteractor.anyStateToAodTransition.filter {
                 it.transitionState == TransitionState.FINISHED
             }.collect {
-                dozeAmount = 1f
-                clock?.animations?.doze(dozeAmount)
+                handleDoze(1f)
             }
         }
     }
@@ -330,7 +405,54 @@
         }
     }
 
+    class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
+        val predrawListener = ViewTreeObserver.OnPreDrawListener {
+            clockFace.events.onTimeTick()
+            true
+        }
+
+        val secondsRunnable = object : Runnable {
+            override fun run() {
+                if (!isRunning) {
+                    return
+                }
+
+                executor.executeDelayed(this, 990)
+                clockFace.events.onTimeTick()
+            }
+        }
+
+        var isRunning: Boolean = false
+            private set
+
+        fun start() {
+            if (isRunning) {
+                return
+            }
+
+            isRunning = true
+            when (clockFace.events.tickRate) {
+                ClockTickRate.PER_MINUTE -> {/* Handled by KeyguardClockSwitchController */}
+                ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
+                ClockTickRate.PER_FRAME -> {
+                    clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener)
+                    clockFace.view.invalidate()
+                }
+            }
+        }
+
+        fun stop() {
+            if (!isRunning) { return }
+
+            isRunning = false
+            clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener)
+        }
+
+        fun update(shouldRun: Boolean) = if (shouldRun) start() else stop()
+    }
+
     companion object {
         private val TAG = ClockEventController::class.simpleName!!
+        private val DOZE_TICKRATE_THRESHOLD = 0.99f
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 7da27b1..baaef19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -103,6 +103,7 @@
 
     @Override
     public void reset() {
+        super.reset();
         // start fresh
         mDismissing = false;
         mView.resetPasswordText(false /* animate */, false /* announce */);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 88ce2a7..a148aa1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.ClockRegistry;
+import com.android.systemui.shared.regionsampling.RegionSampler;
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -88,7 +89,10 @@
     private final ClockRegistry.ClockChangeListener mClockChangedListener;
 
     private ViewGroup mStatusArea;
-    // If set will replace keyguard_slice_view
+
+    // If the SMARTSPACE flag is set, keyguard_slice_view is replaced by the following views.
+    private ViewGroup mDateWeatherView;
+    private View mWeatherView;
     private View mSmartspaceView;
 
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -192,14 +196,21 @@
 
         if (mSmartspaceController.isEnabled()) {
             View ksv = mView.findViewById(R.id.keyguard_slice_view);
-            int ksvIndex = mStatusArea.indexOfChild(ksv);
+            int viewIndex = mStatusArea.indexOfChild(ksv);
             ksv.setVisibility(View.GONE);
 
-            addSmartspaceView(ksvIndex);
+            // TODO(b/261757708): add content observer for the Settings toggle and add/remove
+            //  weather according to the Settings.
+            if (mSmartspaceController.isDateWeatherDecoupled()) {
+                addDateWeatherView(viewIndex);
+                viewIndex += 1;
+            }
+
+            addSmartspaceView(viewIndex);
         }
 
         mSecureSettings.registerContentObserverForUser(
-                Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
+                Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
                 false, /* notifyForDescendants */
                 mDoubleLineClockObserver,
                 UserHandle.USER_ALL
@@ -229,6 +240,14 @@
 
     void onLocaleListChanged() {
         if (mSmartspaceController.isEnabled()) {
+            if (mSmartspaceController.isDateWeatherDecoupled()) {
+                mDateWeatherView.removeView(mWeatherView);
+                int index = mStatusArea.indexOfChild(mDateWeatherView);
+                if (index >= 0) {
+                    mStatusArea.removeView(mDateWeatherView);
+                    addDateWeatherView(index);
+                }
+            }
             int index = mStatusArea.indexOfChild(mSmartspaceView);
             if (index >= 0) {
                 mStatusArea.removeView(mSmartspaceView);
@@ -237,6 +256,30 @@
         }
     }
 
+    private void addDateWeatherView(int index) {
+        mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                MATCH_PARENT, WRAP_CONTENT);
+        mStatusArea.addView(mDateWeatherView, index, lp);
+        int startPadding = getContext().getResources().getDimensionPixelSize(
+                R.dimen.below_clock_padding_start);
+        int endPadding = getContext().getResources().getDimensionPixelSize(
+                R.dimen.below_clock_padding_end);
+        mDateWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
+
+        addWeatherView();
+    }
+
+    private void addWeatherView() {
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                WRAP_CONTENT, WRAP_CONTENT);
+        mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
+        // Place weather right after the date, before the extras
+        final int index = mDateWeatherView.getChildCount() == 0 ? 0 : 1;
+        mDateWeatherView.addView(mWeatherView, index, lp);
+        mWeatherView.setPaddingRelative(0, 0, 4, 0);
+    }
+
     private void addSmartspaceView(int index) {
         mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
@@ -301,7 +344,8 @@
         }
         ClockController clock = getClock();
         if (clock != null) {
-            clock.getEvents().onTimeTick();
+            clock.getSmallClock().getEvents().onTimeTick();
+            clock.getLargeClock().getEvents().onTimeTick();
         }
     }
 
@@ -424,6 +468,10 @@
         if (clock != null) {
             clock.dump(pw);
         }
+        final RegionSampler regionSampler = mClockEventController.getRegionSampler();
+        if (regionSampler != null) {
+            regionSampler.dump(pw);
+        }
     }
 
     /** Gets the animations for the current clock. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 02776a2..ec8fa92 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -15,8 +15,6 @@
  */
 package com.android.keyguard;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import android.app.Presentation;
 import android.content.Context;
 import android.graphics.Color;
@@ -37,9 +35,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
+import com.android.systemui.settings.DisplayTracker;
 
 import java.util.concurrent.Executor;
 
@@ -53,6 +53,7 @@
 
     private MediaRouter mMediaRouter = null;
     private final DisplayManager mDisplayService;
+    private final DisplayTracker mDisplayTracker;
     private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     private final Context mContext;
@@ -62,46 +63,43 @@
 
     private final SparseArray<Presentation> mPresentations = new SparseArray<>();
 
-    private final DisplayManager.DisplayListener mDisplayListener =
-            new DisplayManager.DisplayListener() {
+    private final DisplayTracker.Callback mDisplayCallback =
+            new DisplayTracker.Callback() {
+                @Override
+                public void onDisplayAdded(int displayId) {
+                    Trace.beginSection(
+                            "KeyguardDisplayManager#onDisplayAdded(displayId=" + displayId + ")");
+                    final Display display = mDisplayService.getDisplay(displayId);
+                    if (mShowing) {
+                        updateNavigationBarVisibility(displayId, false /* navBarVisible */);
+                        showPresentation(display);
+                    }
+                    Trace.endSection();
+                }
 
-        @Override
-        public void onDisplayAdded(int displayId) {
-            Trace.beginSection(
-                    "KeyguardDisplayManager#onDisplayAdded(displayId=" + displayId + ")");
-            final Display display = mDisplayService.getDisplay(displayId);
-            if (mShowing) {
-                updateNavigationBarVisibility(displayId, false /* navBarVisible */);
-                showPresentation(display);
-            }
-            Trace.endSection();
-        }
-
-        @Override
-        public void onDisplayChanged(int displayId) {
-
-        }
-
-        @Override
-        public void onDisplayRemoved(int displayId) {
-            Trace.beginSection(
-                    "KeyguardDisplayManager#onDisplayRemoved(displayId=" + displayId + ")");
-            hidePresentation(displayId);
-            Trace.endSection();
-        }
-    };
+                @Override
+                public void onDisplayRemoved(int displayId) {
+                    Trace.beginSection(
+                            "KeyguardDisplayManager#onDisplayRemoved(displayId=" + displayId + ")");
+                    hidePresentation(displayId);
+                    Trace.endSection();
+                }
+            };
 
     @Inject
     public KeyguardDisplayManager(Context context,
             Lazy<NavigationBarController> navigationBarControllerLazy,
             KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
+            DisplayTracker displayTracker,
+            @Main Executor mainExecutor,
             @UiBackground Executor uiBgExecutor) {
         mContext = context;
         mNavigationBarControllerLazy = navigationBarControllerLazy;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
         mDisplayService = mContext.getSystemService(DisplayManager.class);
-        mDisplayService.registerDisplayListener(mDisplayListener, null /* handler */);
+        mDisplayTracker = displayTracker;
+        mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
     }
 
     private boolean isKeyguardShowable(Display display) {
@@ -109,7 +107,7 @@
             if (DEBUG) Log.i(TAG, "Cannot show Keyguard on null display");
             return false;
         }
-        if (display.getDisplayId() == DEFAULT_DISPLAY) {
+        if (display.getDisplayId() == mDisplayTracker.getDefaultDisplayId()) {
             if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on the default display");
             return false;
         }
@@ -224,7 +222,7 @@
     protected boolean updateDisplays(boolean showing) {
         boolean changed = false;
         if (showing) {
-            final Display[] displays = mDisplayService.getDisplays();
+            final Display[] displays = mDisplayTracker.getAllDisplays();
             for (Display display : displays) {
                 int displayId = display.getDisplayId();
                 updateNavigationBarVisibility(displayId, false /* navBarVisible */);
@@ -247,7 +245,7 @@
     //  term solution in R.
     private void updateNavigationBarVisibility(int displayId, boolean navBarVisible) {
         // Leave this task to {@link StatusBarKeyguardViewManager}
-        if (displayId == DEFAULT_DISPLAY) return;
+        if (displayId == mDisplayTracker.getDefaultDisplayId()) return;
 
         NavigationBarView navBarView = mNavigationBarControllerLazy.get()
                 .getNavigationBarView(displayId);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 08e9cf6..2a389b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.widget.FrameLayout;
 
 /**
@@ -33,7 +34,7 @@
 public class KeyguardHostView extends FrameLayout {
 
     protected ViewMediatorCallback mViewMediatorCallback;
-
+    private boolean mIsInteractable;
 
     public KeyguardHostView(Context context) {
         this(context, null);
@@ -54,4 +55,24 @@
     public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
         mViewMediatorCallback = viewMediatorCallback;
     }
+
+    /** Set true if the view can be interacted with */
+    public void setInteractable(boolean isInteractable) {
+        mIsInteractable = isInteractable;
+    }
+
+    /**
+     * Make sure to disallow touches while transitioning the bouncer, otherwise
+     * it can remain interactable even when barely visible.
+     */
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return !mIsInteractable;
+    }
+
+    /** True to consume any events that are sent to it */
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return true;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index d4ca8e3..6139403 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -29,6 +29,9 @@
 import android.view.View.OnKeyListener;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
+import android.window.OnBackAnimationCallback;
+
+import androidx.annotation.NonNull;
 
 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -394,6 +397,14 @@
     }
 
     /**
+     * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture.
+     */
+    @NonNull
+    public OnBackAnimationCallback getBackCallback() {
+        return mKeyguardSecurityContainerController.getBackCallback();
+    }
+
+    /**
      * Allows the media keys to work when the keyguard is showing.
      * The media keys should be of no interest to the actual keyguard view(s),
      * so intercepting them here should not be of any harm.
@@ -516,4 +527,9 @@
             mKeyguardSecurityContainerController.updateKeyguardPosition(x);
         }
     }
+
+    /** Set true if the view can be interacted with */
+    public void setInteractable(boolean isInteractable) {
+        mView.setInteractable(isInteractable);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index d1c9a30..b143c5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -121,6 +121,7 @@
 
     @Override
     public void reset() {
+        mMessageAreaController.setMessage("", false);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5d7a6f1..9f07a20 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -32,6 +32,7 @@
 import static androidx.constraintlayout.widget.ConstraintSet.TOP;
 import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
 
+import static com.android.systemui.animation.InterpolatorsAndroidX.DECELERATE_QUINT;
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 
 import static java.lang.Integer.max;
@@ -73,6 +74,8 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -89,6 +92,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.settingslib.Utils;
+import com.android.settingslib.drawable.CircleFramedDrawable;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
@@ -135,7 +139,9 @@
     private static final float MIN_DRAG_SIZE = 10;
     // How much to scale the default slop by, to avoid accidental drags.
     private static final float SLOP_SCALE = 4f;
-
+    @VisibleForTesting
+    // How much the view scales down to during back gestures.
+    static final float MIN_BACK_SCALE = 0.9f;
     @VisibleForTesting
     KeyguardSecurityViewFlipper mSecurityViewFlipper;
     private GlobalSettings mGlobalSettings;
@@ -230,16 +236,35 @@
                     }
                     updateChildren(0 /* translationY */, 1f /* alpha */);
                 }
-
-                private void updateChildren(int translationY, float alpha) {
-                    for (int i = 0; i < KeyguardSecurityContainer.this.getChildCount(); ++i) {
-                        View child = KeyguardSecurityContainer.this.getChildAt(i);
-                        child.setTranslationY(translationY);
-                        child.setAlpha(alpha);
-                    }
-                }
             };
 
+    private final OnBackAnimationCallback mBackCallback = new OnBackAnimationCallback() {
+        @Override
+        public void onBackCancelled() {
+            // TODO(b/259608500): Remove once back API auto animates progress to 0 on cancel.
+            resetScale();
+        }
+
+        @Override
+        public void onBackInvoked() { }
+
+        @Override
+        public void onBackProgressed(BackEvent event) {
+            float progress = event.getProgress();
+            // TODO(b/263819310): Update the interpolator to match spec.
+            float scale = MIN_BACK_SCALE
+                    +  (1 - MIN_BACK_SCALE) * (1 - DECELERATE_QUINT.getInterpolation(progress));
+            setScale(scale);
+        }
+    };
+    /**
+     * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture.
+     */
+    @NonNull
+    OnBackAnimationCallback getBackCallback() {
+        return mBackCallback;
+    }
+
     // Used to notify the container when something interesting happens.
     public interface SecurityCallback {
         /**
@@ -562,6 +587,7 @@
      * This will run when the bouncer shows in all cases except when the user drags the bouncer up.
      */
     public void startAppearAnimation(SecurityMode securityMode) {
+        updateChildren(0 /* translationY */, 1f /* alpha */);
         mViewMode.startAppearAnimation(securityMode);
     }
 
@@ -736,6 +762,23 @@
         mViewMode.onDensityOrFontScaleChanged();
     }
 
+    void resetScale() {
+        setScale(1);
+    }
+
+    private void setScale(float scale) {
+        setScaleX(scale);
+        setScaleY(scale);
+    }
+
+    private void updateChildren(int translationY, float alpha) {
+        for (int i = 0; i < getChildCount(); ++i) {
+            View child = getChildAt(i);
+            child.setTranslationY(translationY);
+            child.setAlpha(alpha);
+        }
+    }
+
     /**
      * Enscapsulates the differences between bouncer modes for the container.
      */
@@ -957,8 +1000,10 @@
         private Drawable findUserIcon(int userId) {
             Bitmap userIcon = UserManager.get(mView.getContext()).getUserIcon(userId);
             if (userIcon != null) {
-                return new BitmapDrawable(userIcon);
+                return CircleFramedDrawable.getInstance(mView.getContext(),
+                        userIcon);
             }
+
             return UserIcons.getDefaultUserIcon(mResources, userId, false);
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index a72a484..57bfe54 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -40,7 +40,9 @@
 import android.util.Slog;
 import android.view.MotionEvent;
 import android.view.View;
+import android.window.OnBackAnimationCallback;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -479,6 +481,9 @@
     /** Called when the bouncer changes visibility. */
     public void onBouncerVisibilityChanged(@View.Visibility int visibility) {
         setBouncerVisible(visibility == View.VISIBLE);
+        if (visibility == View.INVISIBLE) {
+            mView.resetScale();
+        }
     }
 
     private void setBouncerVisible(boolean visible) {
@@ -588,6 +593,14 @@
     }
 
     /**
+     * @return the {@link OnBackAnimationCallback} to animate this view during a back gesture.
+     */
+    @NonNull
+    OnBackAnimationCallback getBackCallback() {
+        return mView.getBackCallback();
+    }
+
+    /**
      * Switches to the given security view unless it's already being shown, in which case
      * this is a no-op.
      *
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 0b2b121..e3de8c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -17,7 +17,6 @@
 package com.android.keyguard;
 
 import static android.app.slice.Slice.HINT_LIST_ITEM;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.PendingIntent;
 import android.net.Uri;
@@ -43,6 +42,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.ViewController;
@@ -64,6 +64,7 @@
     private final ConfigurationController mConfigurationController;
     private final TunerService mTunerService;
     private final DumpManager mDumpManager;
+    private final DisplayTracker mDisplayTracker;
     private int mDisplayId;
     private LiveData<Slice> mLiveData;
     private Uri mKeyguardSliceUri;
@@ -108,12 +109,14 @@
             ActivityStarter activityStarter,
             ConfigurationController configurationController,
             TunerService tunerService,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            DisplayTracker displayTracker) {
         super(keyguardSliceView);
         mActivityStarter = activityStarter;
         mConfigurationController = configurationController;
         mTunerService = tunerService;
         mDumpManager = dumpManager;
+        mDisplayTracker = displayTracker;
     }
 
     @Override
@@ -124,7 +127,7 @@
         }
         mTunerService.addTunable(mTunable, Settings.Secure.KEYGUARD_SLICE_URI);
         // Make sure we always have the most current slice
-        if (mDisplayId == DEFAULT_DISPLAY && mLiveData != null) {
+        if (mDisplayId == mDisplayTracker.getDefaultDisplayId() && mLiveData != null) {
             mLiveData.observeForever(mObserver);
         }
         mConfigurationController.addCallback(mConfigurationListener);
@@ -137,7 +140,7 @@
     @Override
     protected void onViewDetached() {
         // TODO(b/117344873) Remove below work around after this issue be fixed.
-        if (mDisplayId == DEFAULT_DISPLAY) {
+        if (mDisplayId == mDisplayTracker.getDefaultDisplayId()) {
             mLiveData.removeObserver(mObserver);
         }
         mTunerService.removeTunable(mTunable);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 204f09e..c68f295 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -26,7 +26,6 @@
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
 import static android.hardware.biometrics.BiometricConstants.LockoutMode;
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
 import static android.hardware.biometrics.BiometricSourceType.FACE;
 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
@@ -86,7 +85,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
@@ -110,8 +108,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -156,6 +152,7 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.Weather;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -274,21 +271,6 @@
     private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName(
             "com.android.settings", "com.android.settings.FallbackHome");
 
-    /**
-     * If true, the system is in the half-boot-to-decryption-screen state.
-     * Prudently disable lockscreen.
-     */
-    public static final boolean CORE_APPS_ONLY;
-
-    static {
-        try {
-            CORE_APPS_ONLY = IPackageManager.Stub.asInterface(
-                    ServiceManager.getService("package")).isOnlyCoreApps();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     private final Context mContext;
     private final UserTracker mUserTracker;
     private final KeyguardUpdateMonitorLogger mLogger;
@@ -369,7 +351,6 @@
     private final Executor mBackgroundExecutor;
     private final SensorPrivacyManager mSensorPrivacyManager;
     private final ActiveUnlockConfig mActiveUnlockConfig;
-    private final PowerManager mPowerManager;
     private final IDreamManager mDreamManager;
     private final TelephonyManager mTelephonyManager;
     @Nullable
@@ -377,7 +358,6 @@
     @Nullable
     private final FaceManager mFaceManager;
     private final LockPatternUtils mLockPatternUtils;
-    private final boolean mWakeOnFingerprintAcquiredStart;
     @VisibleForTesting
     @DevicePostureController.DevicePostureInt
     protected int mConfigFaceAuthSupportedPosture;
@@ -901,11 +881,6 @@
     private void handleFingerprintAcquired(
             @BiometricFingerprintConstants.FingerprintAcquired int acquireInfo) {
         Assert.isMainThread();
-        if (mWakeOnFingerprintAcquiredStart && acquireInfo == FINGERPRINT_ACQUIRED_START) {
-            mPowerManager.wakeUp(
-                    SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC,
-                    "com.android.systemui.keyguard:FINGERPRINT_ACQUIRED_START");
-        }
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1554,6 +1529,7 @@
     @VisibleForTesting
     void setAssistantVisible(boolean assistantVisible) {
         mAssistantVisible = assistantVisible;
+        mLogger.logAssistantVisible(mAssistantVisible);
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                 FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
         if (mAssistantVisible) {
@@ -1961,6 +1937,11 @@
             }
         }
         mGoingToSleep = true;
+        // Resetting assistant visibility state as the device is going to sleep now.
+        // TaskStackChangeListener gets triggered a little late when we transition to AoD,
+        // which results in face auth running once on AoD.
+        mAssistantVisible = false;
+        mLogger.d("Started going to sleep, mGoingToSleep=true, mAssistantVisible=false");
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_GOING_TO_SLEEP);
     }
 
@@ -2066,7 +2047,6 @@
             UiEventLogger uiEventLogger,
             // This has to be a provider because SessionTracker depends on KeyguardUpdateMonitor :(
             Provider<SessionTracker> sessionTrackerProvider,
-            PowerManager powerManager,
             TrustManager trustManager,
             SubscriptionManager subscriptionManager,
             UserManager userManager,
@@ -2103,7 +2083,6 @@
         mLogger = logger;
         mUiEventLogger = uiEventLogger;
         mSessionTrackerProvider = sessionTrackerProvider;
-        mPowerManager = powerManager;
         mTrustManager = trustManager;
         mUserManager = userManager;
         mDreamManager = dreamManager;
@@ -2114,8 +2093,6 @@
         mFpm = fingerprintManager;
         mFaceManager = faceManager;
         mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
-        mWakeOnFingerprintAcquiredStart = context.getResources()
-                        .getBoolean(com.android.internal.R.bool.kg_wake_on_acquire_start);
         mFaceAcquiredInfoIgnoreList = Arrays.stream(
                 mContext.getResources().getIntArray(
                         R.array.config_face_acquire_device_entry_ignorelist))
@@ -2695,7 +2672,10 @@
 
     private boolean shouldListenForFaceAssistant() {
         BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser());
-        return mAssistantVisible && mKeyguardOccluded
+        return mAssistantVisible
+                // There can be intermediate states where mKeyguardShowing is false but
+                // mKeyguardOccluded is true, we don't want to run face auth in such a scenario.
+                && (mKeyguardShowing && mKeyguardOccluded)
                 && !(face != null && face.mAuthenticated)
                 && !mUserHasTrust.get(getCurrentUser(), false);
     }
@@ -3237,6 +3217,24 @@
     }
 
     /**
+     * @param data the weather data (temp, conditions, unit) for weather clock to use
+     */
+    public void sendWeatherData(Weather data) {
+        mHandler.post(()-> {
+            handleWeatherDataUpdate(data); });
+    }
+
+    private void handleWeatherDataUpdate(Weather data) {
+        Assert.isMainThread();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onWeatherDataChanged(data);
+            }
+        }
+    }
+
+    /**
      * Handle {@link #MSG_BATTERY_UPDATE}
      */
     private void handleBatteryUpdate(BatteryStatus status) {
@@ -3355,7 +3353,8 @@
     /**
      * Handle {@link #MSG_KEYGUARD_RESET}
      */
-    private void handleKeyguardReset() {
+    @VisibleForTesting
+    protected void handleKeyguardReset() {
         mLogger.d("handleKeyguardReset");
         updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
                 FACE_AUTH_UPDATED_KEYGUARD_RESET);
@@ -3696,6 +3695,7 @@
                 if (info == null) {
                     return;
                 }
+                mLogger.logTaskStackChangedForAssistant(info.visible);
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED,
                         info.visible));
             } catch (RemoteException e) {
@@ -3894,7 +3894,6 @@
         pw.println("  getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
         pw.println("  getUserUnlockedWithBiometric()="
                 + getUserUnlockedWithBiometric(getCurrentUser()));
-        pw.println("  mWakeOnFingerprintAcquiredStart=" + mWakeOnFingerprintAcquiredStart);
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
             pw.println("    " + data.toString());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index e6b9ac8..4a7dd24 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -24,6 +24,7 @@
 
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.Weather;
 
 import java.util.TimeZone;
 
@@ -58,6 +59,11 @@
     public void onTimeFormatChanged(String timeFormat) { }
 
     /**
+     * Called when receive new weather data.
+     */
+    public void onWeatherDataChanged(Weather data) { }
+
+    /**
      * Called when the carrier PLMN or SPN changes.
      */
     public void onRefreshCarrierInfo() { }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 1322f16..8071a5d 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -429,6 +429,7 @@
         pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
         pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
         pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
+        pw.println(" mDefaultPaddingPx: " + mDefaultPaddingPx);
 
         if (mView != null) {
             mView.dump(pw, args);
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index b159714..35cae09 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -47,7 +47,6 @@
 import com.android.systemui.R;
 
 import java.util.ArrayList;
-import java.util.Stack;
 
 /**
  * A View similar to a textView which contains password text and can animate when the text is
@@ -92,7 +91,6 @@
     private final int mGravity;
     private ArrayList<CharState> mTextChars = new ArrayList<>();
     private String mText = "";
-    private Stack<CharState> mCharPool = new Stack<>();
     private int mDotSize;
     private PowerManager mPM;
     private int mCharPadding;
@@ -310,13 +308,7 @@
     }
 
     private CharState obtainCharState(char c) {
-        CharState charState;
-        if(mCharPool.isEmpty()) {
-            charState = new CharState();
-        } else {
-            charState = mCharPool.pop();
-            charState.reset();
-        }
+        CharState charState = new CharState();
         charState.whichChar = c;
         return charState;
     }
@@ -343,8 +335,6 @@
                 maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION;
                 charState.startRemoveAnimation(startDelay, maxDelay);
                 charState.removeDotSwapCallbacks();
-            } else {
-                mCharPool.push(charState);
             }
         }
         if (!animated) {
@@ -421,8 +411,6 @@
             public void onAnimationEnd(Animator animation) {
                 if (!mCancelled) {
                     mTextChars.remove(CharState.this);
-                    mCharPool.push(CharState.this);
-                    reset();
                     cancelAnimator(textTranslateAnimator);
                     textTranslateAnimator = null;
                 }
@@ -518,21 +506,6 @@
             }
         };
 
-        void reset() {
-            whichChar = 0;
-            currentTextSizeFactor = 0.0f;
-            currentDotSizeFactor = 0.0f;
-            currentWidthFactor = 0.0f;
-            cancelAnimator(textAnimator);
-            textAnimator = null;
-            cancelAnimator(dotAnimator);
-            dotAnimator = null;
-            cancelAnimator(widthAnimator);
-            widthAnimator = null;
-            currentTextTranslationY = 1.0f;
-            removeDotSwapCallbacks();
-        }
-
         void startRemoveAnimation(long startDelay, long widthDelay) {
             boolean dotNeedsAnimation = (currentDotSizeFactor > 0.0f && dotAnimator == null)
                     || (dotAnimator != null && dotAnimationIsGrowing);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
index 0cbf8bc..5ad21df 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerComponent.java
@@ -20,13 +20,13 @@
 
 import com.android.keyguard.KeyguardHostViewController;
 import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 
 import dagger.BindsInstance;
 import dagger.Subcomponent;
 
 /**
- * Dagger Subcomponent for the {@link KeyguardBouncer}.
+ * Dagger Subcomponent for the {@link PrimaryBouncerInteractor}.
  */
 @Subcomponent(modules = {KeyguardBouncerModule.class})
 @KeyguardBouncerScope
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index ef067b8..cb7a0a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -29,7 +29,7 @@
 import com.android.systemui.R;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 
 import java.util.Optional;
 
@@ -39,7 +39,7 @@
 import dagger.Provides;
 
 /**
- * Module to create and access view related to the {@link KeyguardBouncer}.
+ * Module to create and access view related to the {@link PrimaryBouncerInteractor}.
  */
 @Module
 public interface KeyguardBouncerModule {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 5b42455..201a1d9 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -431,4 +431,20 @@
             str1 = PowerManager.wakeReasonToString(pmWakeReason)
         }, { "Skip updating face listening state on wakeup from $str1"})
     }
+
+    fun logTaskStackChangedForAssistant(assistantVisible: Boolean) {
+        logBuffer.log(TAG, VERBOSE, {
+            bool1 = assistantVisible
+        }, {
+            "TaskStackChanged for ACTIVITY_TYPE_ASSISTANT, assistant visible: $bool1"
+        })
+    }
+
+    fun logAssistantVisible(assistantVisible: Boolean) {
+        logBuffer.log(TAG, VERBOSE, {
+            bool1 = assistantVisible
+        }, {
+            "Updating mAssistantVisible to new value: $bool1"
+        })
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
new file mode 100644
index 0000000..249b3fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+/** Logging helper for trust repository. */
+@SysUISingleton
+class TrustRepositoryLogger
+@Inject
+constructor(
+    @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer,
+) {
+    fun onTrustChanged(
+        enabled: Boolean,
+        newlyUnlocked: Boolean,
+        userId: Int,
+        flags: Int,
+        trustGrantedMessages: List<String>?
+    ) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                bool1 = enabled
+                bool2 = newlyUnlocked
+                int1 = userId
+                int2 = flags
+                str1 = trustGrantedMessages?.joinToString()
+            },
+            {
+                "onTrustChanged enabled: $bool1, newlyUnlocked: $bool2, " +
+                    "userId: $int1, flags: $int2, grantMessages: $str1"
+            }
+        )
+    }
+
+    fun trustListenerRegistered() {
+        logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#registerTrustListener")
+    }
+
+    fun trustListenerUnregistered() {
+        logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#unregisterTrustListener")
+    }
+
+    fun trustModelEmitted(value: TrustModel) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = value.userId
+                bool1 = value.isTrusted
+            },
+            { "trustModel emitted: userId: $int1 isTrusted: $bool1" }
+        )
+    }
+
+    fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { bool1 = isCurrentUserTrusted },
+            { "isCurrentUserTrusted emitted: $bool1" }
+        )
+    }
+
+    companion object {
+        const val TAG = "TrustRepositoryLog"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
index 9ac45b3..227f0ace 100644
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
@@ -34,7 +34,7 @@
     override fun start() {
         coroutineScope.launch {
             val listener = FlagListenable.Listener { event ->
-                if (event.flagId == Flags.CHOOSER_UNBUNDLED.id) {
+                if (event.flagName == Flags.CHOOSER_UNBUNDLED.name) {
                     launch { updateUnbundledChooserEnabled() }
                     event.requestNoRestart()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 3e0fa45..54939fd 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -35,6 +35,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
 import com.android.systemui.animation.Interpolators
+import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import java.util.concurrent.Executor
 
@@ -47,7 +48,8 @@
     pos: Int,
     val statusBarStateController: StatusBarStateController,
     val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    val mainExecutor: Executor
+    val mainExecutor: Executor,
+    val logger: ScreenDecorationsLogger,
 ) : ScreenDecorations.DisplayCutoutView(context, pos) {
     private var showScanningAnim = false
     private val rimPaint = Paint()
@@ -55,6 +57,7 @@
     private var rimAnimator: AnimatorSet? = null
     private val rimRect = RectF()
     private var cameraProtectionColor = Color.BLACK
+
     var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context,
             R.attr.wallpaperTextColorAccent)
     private var cameraProtectionAnimator: ValueAnimator? = null
@@ -175,15 +178,22 @@
         }
         if (showScanningAnim) {
             // Make sure that our measured height encompasses the extra space for the animation
-            mTotalBounds.union(mBoundingRect)
+            mTotalBounds.set(mBoundingRect)
             mTotalBounds.union(
                 rimRect.left.toInt(),
                 rimRect.top.toInt(),
                 rimRect.right.toInt(),
                 rimRect.bottom.toInt())
-            setMeasuredDimension(
-                resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0),
-                resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0))
+            val measuredWidth = resolveSizeAndState(mTotalBounds.width(), widthMeasureSpec, 0)
+            val measuredHeight = resolveSizeAndState(mTotalBounds.height(), heightMeasureSpec, 0)
+            logger.boundingRect(rimRect, "onMeasure: Face scanning animation")
+            logger.boundingRect(mBoundingRect, "onMeasure: Display cutout view bounding rect")
+            logger.boundingRect(mTotalBounds, "onMeasure: TotalBounds")
+            logger.onMeasureDimensions(widthMeasureSpec,
+                    heightMeasureSpec,
+                    measuredWidth,
+                    measuredHeight)
+            setMeasuredDimension(measuredWidth, measuredHeight)
         } else {
             setMeasuredDimension(
                 resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e6f559b..fb65588 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -36,10 +36,10 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.hardware.display.DisplayManager;
 import android.hardware.graphics.common.AlphaInterpretation;
 import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.provider.Settings.Secure;
@@ -64,6 +64,7 @@
 
 import com.android.internal.util.Preconditions;
 import com.android.settingslib.Utils;
+import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
@@ -75,7 +76,9 @@
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.log.ScreenDecorationsLogger;
 import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
@@ -119,8 +122,11 @@
             R.id.display_cutout_right,
             R.id.display_cutout_bottom
     };
+    private final ScreenDecorationsLogger mLogger;
 
-    private DisplayManager mDisplayManager;
+    private final AuthController mAuthController;
+
+    private DisplayTracker mDisplayTracker;
     @VisibleForTesting
     protected boolean mIsRegistered;
     private final Context mContext;
@@ -128,7 +134,7 @@
     private final TunerService mTunerService;
     private final SecureSettings mSecureSettings;
     @VisibleForTesting
-    DisplayManager.DisplayListener mDisplayListener;
+    DisplayTracker.Callback mDisplayListener;
     private CameraAvailabilityListener mCameraListener;
     private final UserTracker mUserTracker;
     private final PrivacyDotViewController mDotViewController;
@@ -152,6 +158,7 @@
     private WindowManager mWindowManager;
     private int mRotation;
     private SettingObserver mColorInversionSetting;
+    @Nullable
     private DelayableExecutor mExecutor;
     private Handler mHandler;
     boolean mPendingConfigChange;
@@ -171,6 +178,7 @@
             DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(
                     mFaceScanningViewId);
             if (overlay != null) {
+                mLogger.cameraProtectionBoundsForScanningOverlay(bounds);
                 overlay.setProtection(protectionPath, bounds);
                 overlay.enableShowProtection(true);
                 updateOverlayWindowVisibilityIfViewExists(
@@ -183,6 +191,7 @@
         }
 
         if (mScreenDecorHwcLayer != null) {
+            mLogger.hwcLayerCameraProtectionBounds(bounds);
             mScreenDecorHwcLayer.setProtection(protectionPath, bounds);
             mScreenDecorHwcLayer.enableShowProtection(true);
             return;
@@ -196,11 +205,12 @@
             }
             ++setProtectionCnt;
             final DisplayCutoutView dcv = (DisplayCutoutView) view;
+            mLogger.dcvCameraBounds(id, bounds);
             dcv.setProtection(protectionPath, bounds);
             dcv.enableShowProtection(true);
         }
         if (setProtectionCnt == 0) {
-            Log.e(TAG, "CutoutView not initialized showCameraProtection");
+            mLogger.cutoutViewNotInitialized();
         }
     }
 
@@ -302,22 +312,41 @@
             SecureSettings secureSettings,
             TunerService tunerService,
             UserTracker userTracker,
+            DisplayTracker displayTracker,
             PrivacyDotViewController dotViewController,
             ThreadFactory threadFactory,
             PrivacyDotDecorProviderFactory dotFactory,
-            FaceScanningProviderFactory faceScanningFactory) {
+            FaceScanningProviderFactory faceScanningFactory,
+            ScreenDecorationsLogger logger,
+            AuthController authController) {
         mContext = context;
         mMainExecutor = mainExecutor;
         mSecureSettings = secureSettings;
         mTunerService = tunerService;
         mUserTracker = userTracker;
+        mDisplayTracker = displayTracker;
         mDotViewController = dotViewController;
         mThreadFactory = threadFactory;
         mDotFactory = dotFactory;
         mFaceScanningFactory = faceScanningFactory;
         mFaceScanningViewId = com.android.systemui.R.id.face_scanning_anim;
+        mLogger = logger;
+        mAuthController = authController;
     }
 
+
+    private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+        @Override
+        public void onFaceSensorLocationChanged() {
+            mLogger.onSensorLocationChanged();
+            if (mExecutor != null) {
+                mExecutor.execute(
+                        () -> updateOverlayProviderViews(
+                                new Integer[]{mFaceScanningViewId}));
+            }
+        }
+    };
+
     @Override
     public void start() {
         if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
@@ -328,6 +357,7 @@
         mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
         mExecutor.execute(this::startOnScreenDecorationsThread);
         mDotViewController.setUiExecutor(mExecutor);
+        mAuthController.addCallback(mAuthControllerCallback);
     }
 
     private boolean isPrivacyDotEnabled() {
@@ -376,7 +406,6 @@
     private void startOnScreenDecorationsThread() {
         Trace.beginSection("ScreenDecorations#startOnScreenDecorationsThread");
         mWindowManager = mContext.getSystemService(WindowManager.class);
-        mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mContext.getDisplay().getDisplayInfo(mDisplayInfo);
         mRotation = mDisplayInfo.rotation;
         mDisplayMode = mDisplayInfo.getMode();
@@ -393,17 +422,7 @@
         setupDecorations();
         setupCameraListener();
 
-        mDisplayListener = new DisplayManager.DisplayListener() {
-            @Override
-            public void onDisplayAdded(int displayId) {
-                // do nothing
-            }
-
-            @Override
-            public void onDisplayRemoved(int displayId) {
-                // do nothing
-            }
-
+        mDisplayListener = new DisplayTracker.Callback() {
             @Override
             public void onDisplayChanged(int displayId) {
                 mContext.getDisplay().getDisplayInfo(mDisplayInfo);
@@ -474,8 +493,7 @@
                 }
             }
         };
-
-        mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+        mDisplayTracker.addDisplayChangeCallback(mDisplayListener, new HandlerExecutor(mHandler));
         updateConfiguration();
         Trace.endSection();
     }
@@ -1315,7 +1333,7 @@
 
             if (showProtection) {
                 // Make sure that our measured height encompasses the protection
-                mTotalBounds.union(mBoundingRect);
+                mTotalBounds.set(mBoundingRect);
                 mTotalBounds.union((int) protectionRect.left, (int) protectionRect.top,
                         (int) protectionRect.right, (int) protectionRect.bottom);
                 setMeasuredDimension(
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 191ac76..b62217f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -40,6 +40,7 @@
 import android.util.TimingsTraceLog;
 import android.view.SurfaceControl;
 import android.view.ThreadedRenderer;
+import android.view.View;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.systemui.dagger.GlobalRootComponent;
@@ -114,6 +115,11 @@
         // the theme set there.
         setTheme(R.style.Theme_SystemUI);
 
+        View.setTraceLayoutSteps(
+                SystemProperties.getBoolean("persist.debug.trace_layouts", false));
+        View.setTracedRequestLayoutClassClass(
+                SystemProperties.get("persist.debug.trace_request_layout_class", null));
+
         if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
             IntentFilter bootCompletedFilter = new
                     IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
index fbb909f..2c97d62 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonModeObserver.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -67,8 +68,8 @@
     }
 
     @Inject
-    public AccessibilityButtonModeObserver(Context context) {
-        super(context, Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
+    public AccessibilityButtonModeObserver(Context context, UserTracker userTracker) {
+        super(context, userTracker, Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
index b32ebcc..53a21b3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserver.java
@@ -23,6 +23,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
 
 import javax.inject.Inject;
 
@@ -48,8 +49,8 @@
     }
 
     @Inject
-    public AccessibilityButtonTargetsObserver(Context context) {
-        super(context, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+    public AccessibilityButtonTargetsObserver(Context context, UserTracker userTracker) {
+        super(context, userTracker, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
index e4e0da6..326773f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java
@@ -27,6 +27,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.settings.UserTracker;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -44,6 +45,7 @@
 public abstract class SecureSettingsContentObserver<T> {
 
     private final ContentResolver mContentResolver;
+    private final UserTracker mUserTracker;
     @VisibleForTesting
     final ContentObserver mContentObserver;
 
@@ -52,9 +54,11 @@
     @VisibleForTesting
     final List<T> mListeners = new ArrayList<>();
 
-    protected SecureSettingsContentObserver(Context context, String secureSettingsKey) {
+    protected SecureSettingsContentObserver(Context context, UserTracker userTracker,
+            String secureSettingsKey) {
         mKey = secureSettingsKey;
         mContentResolver = context.getContentResolver();
+        mUserTracker = userTracker;
         mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
             @Override
             public void onChange(boolean selfChange) {
@@ -103,7 +107,7 @@
      * See {@link Settings.Secure}.
      */
     public final String getSettingsValue() {
-        return Settings.Secure.getStringForUser(mContentResolver, mKey, UserHandle.USER_CURRENT);
+        return Settings.Secure.getStringForUser(mContentResolver, mKey, mUserTracker.getUserId());
     }
 
     private void updateValueChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index dab73e9..ddac25b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -35,14 +35,11 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.util.Log;
-import android.view.Display;
 import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
-import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 
@@ -52,6 +49,8 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -179,7 +178,9 @@
 
     private final SystemActionsBroadcastReceiver mReceiver;
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final Optional<Recents> mRecentsOptional;
+    private final DisplayTracker mDisplayTracker;
     private Locale mLocale;
     private final AccessibilityManager mA11yManager;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
@@ -190,13 +191,17 @@
 
     @Inject
     public SystemActions(Context context,
+            UserTracker userTracker,
             NotificationShadeWindowController notificationShadeController,
             ShadeController shadeController,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            Optional<Recents> recentsOptional) {
+            Optional<Recents> recentsOptional,
+            DisplayTracker displayTracker) {
         mContext = context;
+        mUserTracker = userTracker;
         mShadeController = shadeController;
         mRecentsOptional = recentsOptional;
+        mDisplayTracker = displayTracker;
         mReceiver = new SystemActionsBroadcastReceiver();
         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
         mA11yManager = (AccessibilityManager) mContext.getSystemService(
@@ -205,7 +210,8 @@
         // Saving in instance variable since to prevent GC since
         // NotificationShadeWindowController.registerCallback() only keeps weak references.
         mNotificationShadeCallback =
-                (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded) ->
+                (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded,
+                            isDreaming) ->
                         registerOrUnregisterDismissNotificationShadeAction();
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
     }
@@ -343,6 +349,7 @@
 
     /**
      * Register a system action.
+     *
      * @param actionId the action ID to register.
      */
     public void register(int actionId) {
@@ -440,6 +447,7 @@
 
     /**
      * Unregister a system action.
+     *
      * @param actionId the action ID to unregister.
      */
     public void unregister(int actionId) {
@@ -475,7 +483,8 @@
     }
 
     private void handleNotifications() {
-        mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::animateExpandNotificationsPanel);
+        mCentralSurfacesOptionalLazy.get().ifPresent(
+                CentralSurfaces::animateExpandNotificationsPanel);
     }
 
     private void handleQuickSettings() {
@@ -507,7 +516,7 @@
 
     private void handleTakeScreenshot() {
         ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
-        screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+        screenshotHelper.takeScreenshot(
                 SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
     }
 
@@ -517,7 +526,7 @@
 
     private void handleAccessibilityButton() {
         AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked(
-                Display.DEFAULT_DISPLAY);
+                mDisplayTracker.getDefaultDisplayId());
     }
 
     private void handleAccessibilityButtonChooser() {
@@ -525,7 +534,7 @@
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
-        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+        mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
     }
 
     private void handleAccessibilityShortcut() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index ab11fce..b3574bf 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -39,6 +39,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.io.PrintWriter;
@@ -62,6 +63,7 @@
     private final AccessibilityManager mAccessibilityManager;
     private final CommandQueue mCommandQueue;
     private final OverviewProxyService mOverviewProxyService;
+    private final DisplayTracker mDisplayTracker;
 
     private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
     private SysUiState mSysUiState;
@@ -102,7 +104,8 @@
     @Inject
     public WindowMagnification(Context context, @Main Handler mainHandler,
             CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
-            SysUiState sysUiState, OverviewProxyService overviewProxyService) {
+            SysUiState sysUiState, OverviewProxyService overviewProxyService,
+            DisplayTracker displayTracker) {
         mContext = context;
         mHandler = mainHandler;
         mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
@@ -110,6 +113,7 @@
         mModeSwitchesController = modeSwitchesController;
         mSysUiState = sysUiState;
         mOverviewProxyService = overviewProxyService;
+        mDisplayTracker = displayTracker;
         mMagnificationControllerSupplier = new ControllerSupplier(context,
                 mHandler, this, context.getSystemService(DisplayManager.class), sysUiState);
     }
@@ -130,14 +134,14 @@
     private void updateSysUiStateFlag() {
         //TODO(b/187510533): support multi-display once SysuiState supports it.
         final WindowMagnificationController controller =
-                mMagnificationControllerSupplier.valueAt(Display.DEFAULT_DISPLAY);
+                mMagnificationControllerSupplier.valueAt(mDisplayTracker.getDefaultDisplayId());
         if (controller != null) {
             controller.updateSysUIStateFlag();
         } else {
             // The instance is initialized when there is an IPC request. Considering
             // self-crash cases, we need to reset the flag in such situation.
             mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, false)
-                    .commitUpdate(Display.DEFAULT_DISPLAY);
+                    .commitUpdate(mDisplayTracker.getDefaultDisplayId());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
index 9af8300..7441e03 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java
@@ -43,6 +43,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Prefs;
 import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.List;
 
@@ -60,6 +61,7 @@
     private static final float DEFAULT_POSITION_Y_PERCENT = 0.9f;
 
     private final Context mContext;
+    private final SecureSettings mSecureSettings;
     private final AccessibilityFloatingMenuView mMenuView;
     private final MigrationTooltipView mMigrationTooltipView;
     private final DockTooltipView mDockTooltipView;
@@ -77,7 +79,7 @@
             new ContentObserver(mHandler) {
                 @Override
                 public void onChange(boolean selfChange) {
-                    mMenuView.setSizeType(getSizeType(mContext));
+                    mMenuView.setSizeType(getSizeType());
                 }
             };
 
@@ -85,8 +87,8 @@
             new ContentObserver(mHandler) {
                 @Override
                 public void onChange(boolean selfChange) {
-                    mMenuView.updateOpacityWith(isFadeEffectEnabled(mContext),
-                            getOpacityValue(mContext));
+                    mMenuView.updateOpacityWith(isFadeEffectEnabled(),
+                            getOpacityValue());
                 }
             };
 
@@ -98,16 +100,19 @@
                 }
             };
 
-    public AccessibilityFloatingMenu(Context context) {
+    public AccessibilityFloatingMenu(Context context, SecureSettings secureSettings) {
         mContext = context;
+        mSecureSettings = secureSettings;
         mMenuView = new AccessibilityFloatingMenuView(context, getPosition(context));
         mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
         mDockTooltipView = new DockTooltipView(mContext, mMenuView);
     }
 
     @VisibleForTesting
-    AccessibilityFloatingMenu(Context context, AccessibilityFloatingMenuView menuView) {
+    AccessibilityFloatingMenu(Context context, SecureSettings secureSettings,
+            AccessibilityFloatingMenuView menuView) {
         mContext = context;
+        mSecureSettings = secureSettings;
         mMenuView = menuView;
         mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
         mDockTooltipView = new DockTooltipView(mContext, mMenuView);
@@ -130,10 +135,10 @@
 
         mMenuView.show();
         mMenuView.onTargetsChanged(targetList);
-        mMenuView.updateOpacityWith(isFadeEffectEnabled(mContext),
-                getOpacityValue(mContext));
-        mMenuView.setSizeType(getSizeType(mContext));
-        mMenuView.setShapeType(getShapeType(mContext));
+        mMenuView.updateOpacityWith(isFadeEffectEnabled(),
+                getOpacityValue());
+        mMenuView.setSizeType(getSizeType());
+        mMenuView.setShapeType(getShapeType());
         mMenuView.setOnDragEndListener(this::onDragEnd);
 
         showMigrationTooltipIfNecessary();
@@ -170,17 +175,17 @@
     // Migration tooltip was the android S feature. It's just used on the Android version from R
     // to S. In addition, it only shows once.
     private void showMigrationTooltipIfNecessary() {
-        if (isMigrationTooltipPromptEnabled(mContext)) {
+        if (isMigrationTooltipPromptEnabled()) {
             mMigrationTooltipView.show();
 
-            Settings.Secure.putInt(mContext.getContentResolver(),
+            mSecureSettings.putInt(
                     ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, /* disabled */ 0);
         }
     }
 
-    private static boolean isMigrationTooltipPromptEnabled(Context context) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
+    private boolean isMigrationTooltipPromptEnabled() {
+        return mSecureSettings.getInt(
+                ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
                 DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1;
     }
 
@@ -212,57 +217,61 @@
         }
     }
 
-    private static boolean isFadeEffectEnabled(Context context) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+    private boolean isFadeEffectEnabled() {
+        return mSecureSettings.getInt(
+                ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
                 DEFAULT_FADE_EFFECT_IS_ENABLED) == /* enabled */ 1;
     }
 
-    private static float getOpacityValue(Context context) {
-        return Settings.Secure.getFloat(
-                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_OPACITY,
+    private float getOpacityValue() {
+        return mSecureSettings.getFloat(
+                ACCESSIBILITY_FLOATING_MENU_OPACITY,
                 DEFAULT_OPACITY_VALUE);
     }
 
-    private static int getSizeType(Context context) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
+    private int getSizeType() {
+        return mSecureSettings.getInt(
+                ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
     }
 
-    private static int getShapeType(Context context) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
+    private int getShapeType() {
+        return mSecureSettings.getInt(
+                ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
                 ShapeType.OVAL);
     }
 
     private void registerContentObservers() {
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                 /* notifyForDescendants */ false, mContentObserver,
                 UserHandle.USER_CURRENT);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+                /* notifyForDescendants */ false, mContentObserver,
+                UserHandle.USER_CURRENT);
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
                 /* notifyForDescendants */ false, mSizeContentObserver,
                 UserHandle.USER_CURRENT);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
                 /* notifyForDescendants */ false, mFadeOutContentObserver,
                 UserHandle.USER_CURRENT);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY),
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
                 /* notifyForDescendants */ false, mFadeOutContentObserver,
                 UserHandle.USER_CURRENT);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES),
+        mSecureSettings.registerContentObserverForUser(
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                 /* notifyForDescendants */ false,
                 mEnabledA11yServicesContentObserver, UserHandle.USER_CURRENT);
     }
 
     private void unregisterContentObservers() {
-        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
-        mContext.getContentResolver().unregisterContentObserver(mSizeContentObserver);
-        mContext.getContentResolver().unregisterContentObserver(mFadeOutContentObserver);
-        mContext.getContentResolver().unregisterContentObserver(
+        mSecureSettings.unregisterContentObserver(mContentObserver);
+        mSecureSettings.unregisterContentObserver(mSizeContentObserver);
+        mSecureSettings.unregisterContentObserver(mFadeOutContentObserver);
+        mSecureSettings.unregisterContentObserver(
                 mEnabledA11yServicesContentObserver);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 403941f..6216b89 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -31,6 +31,7 @@
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.util.settings.SecureSettings;
 
 import javax.inject.Inject;
 
@@ -44,6 +45,7 @@
     private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
     private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final SecureSettings mSecureSettings;
 
     private Context mContext;
     @VisibleForTesting
@@ -85,11 +87,13 @@
     public AccessibilityFloatingMenuController(Context context,
             AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
             AccessibilityButtonModeObserver accessibilityButtonModeObserver,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            SecureSettings secureSettings) {
         mContext = context;
         mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
         mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mSecureSettings = secureSettings;
 
         mIsKeyguardVisible = false;
     }
@@ -159,7 +163,7 @@
 
     private void showFloatingMenu() {
         if (mFloatingMenu == null) {
-            mFloatingMenu = new AccessibilityFloatingMenu(mContext);
+            mFloatingMenu = new AccessibilityFloatingMenu(mContext, mSecureSettings);
         }
 
         mFloatingMenu.show();
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 7c2673c..1ea173e 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -1,7 +1,5 @@
 package com.android.systemui.assist;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
 
@@ -35,8 +33,11 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.settings.SecureSettings;
 
 import javax.inject.Inject;
 
@@ -119,6 +120,9 @@
     private final UiController mUiController;
     protected final Lazy<SysUiState> mSysUiState;
     protected final AssistLogger mAssistLogger;
+    private final UserTracker mUserTracker;
+    private final DisplayTracker mDisplayTracker;
+    private final SecureSettings mSecureSettings;
 
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final CommandQueue mCommandQueue;
@@ -135,7 +139,10 @@
             Lazy<SysUiState> sysUiState,
             DefaultUiController defaultUiController,
             AssistLogger assistLogger,
-            @Main Handler uiHandler) {
+            @Main Handler uiHandler,
+            UserTracker userTracker,
+            DisplayTracker displayTracker,
+            SecureSettings secureSettings) {
         mContext = context;
         mDeviceProvisionedController = controller;
         mCommandQueue = commandQueue;
@@ -143,6 +150,9 @@
         mAssistDisclosure = new AssistDisclosure(context, uiHandler);
         mPhoneStateMonitor = phoneStateMonitor;
         mAssistLogger = assistLogger;
+        mUserTracker = userTracker;
+        mDisplayTracker = displayTracker;
+        mSecureSettings = secureSettings;
 
         registerVoiceInteractionSessionListener();
 
@@ -206,7 +216,7 @@
                                     .setFlag(
                                             SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
                                             hints.getBoolean(CONSTRAINED_KEY, false))
-                                    .commitUpdate(DEFAULT_DISPLAY);
+                                    .commitUpdate(mDisplayTracker.getDefaultDisplayId());
                         }
                     }
                 });
@@ -273,7 +283,7 @@
                 CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
                 false /* force */);
 
-        boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+        boolean structureEnabled = mSecureSettings.getIntForUser(
                 Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
 
         final SearchManager searchManager =
@@ -300,7 +310,7 @@
                 @Override
                 public void run() {
                     mContext.startActivityAsUser(intent, opts.toBundle(),
-                            new UserHandle(UserHandle.USER_CURRENT));
+                            mUserTracker.getUserHandle());
                 }
             });
         } catch (ActivityNotFoundException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index e4c197f..1404053 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -35,13 +35,13 @@
 
     override val actsAsConfirmButton: Boolean = true
 
-    override fun shouldAnimateForTransition(
+    override fun shouldAnimateIconViewForTransition(
             @BiometricState oldState: Int,
             @BiometricState newState: Int
     ): Boolean = when (newState) {
         STATE_PENDING_CONFIRMATION -> true
         STATE_AUTHENTICATED -> false
-        else -> super.shouldAnimateForTransition(oldState, newState)
+        else -> super.shouldAnimateIconViewForTransition(oldState, newState)
     }
 
     @RawRes
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index b962cc4..1f6f6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -46,6 +46,8 @@
 
     private var isDeviceFolded: Boolean = false
     private val isSideFps: Boolean
+    private val isReverseDefaultRotation =
+            context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
     private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
     var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
         set(value) {
@@ -76,7 +78,7 @@
         isSideFps = sideFps
         val displayInfo = DisplayInfo()
         context.display?.getDisplayInfo(displayInfo)
-        if (isSideFps && displayInfo.rotation == Surface.ROTATION_180) {
+        if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
             iconView.rotation = 180f
         }
         screenSizeFoldProvider.registerCallback(this, context.mainExecutor)
@@ -86,7 +88,7 @@
     private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
         val displayInfo = DisplayInfo()
         context.display?.getDisplayInfo(displayInfo)
-        val rotation = displayInfo.rotation
+        val rotation = getRotationFromDefault(displayInfo.rotation)
         val iconAnimation = getSideFpsAnimationForTransition(rotation)
         val iconViewOverlayAnimation =
                 getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
@@ -104,12 +106,14 @@
 
         iconView.frame = 0
         iconViewOverlay.frame = 0
-        if (shouldAnimateForTransition(lastState, newState)) {
-            iconView.playAnimation()
-            iconViewOverlay.playAnimation()
-        } else if (lastState == STATE_IDLE && newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+        if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
             iconView.playAnimation()
         }
+
+        if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) {
+            iconViewOverlay.playAnimation()
+        }
+
         LottieColorUtils.applyDynamicColors(context, iconView)
         LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
     }
@@ -127,7 +131,7 @@
         }
 
         iconView.frame = 0
-        if (shouldAnimateForTransition(lastState, newState)) {
+        if (shouldAnimateIconViewForTransition(lastState, newState)) {
             iconView.playAnimation()
         }
         LottieColorUtils.applyDynamicColors(context, iconView)
@@ -160,7 +164,32 @@
         return if (id != null) context.getString(id) else null
     }
 
-    protected open fun shouldAnimateForTransition(
+    protected open fun shouldAnimateIconViewForTransition(
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
+    ) = when (newState) {
+        STATE_HELP,
+        STATE_ERROR -> true
+        STATE_AUTHENTICATING_ANIMATING_IN,
+        STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
+        STATE_AUTHENTICATED -> true
+        else -> false
+    }
+
+    private fun shouldAnimateSfpsIconViewForTransition(
+            @BiometricState oldState: Int,
+            @BiometricState newState: Int
+    ) = when (newState) {
+        STATE_HELP,
+        STATE_ERROR -> true
+        STATE_AUTHENTICATING_ANIMATING_IN,
+        STATE_AUTHENTICATING ->
+            oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
+        STATE_AUTHENTICATED -> true
+        else -> false
+    }
+
+    protected open fun shouldAnimateIconViewOverlayForTransition(
             @BiometricState oldState: Int,
             @BiometricState newState: Int
     ) = when (newState) {
@@ -202,6 +231,9 @@
         return if (id != null) return id else null
     }
 
+    private fun getRotationFromDefault(rotation: Int): Int =
+            if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
+
     @RawRes
     private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) {
         Surface.ROTATION_90 -> if (isDeviceFolded) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 68e1f72..febf75e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -847,7 +847,7 @@
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
+                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
                 windowFlags,
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 4b57d45..53ab6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -55,11 +55,11 @@
     private val fadeDuration = 83L
     private val retractDuration = 400L
     private var alphaInDuration: Long = 0
-    private var unlockedRippleInProgress: Boolean = false
     private val dwellShader = DwellRippleShader()
     private val dwellPaint = Paint()
     private val rippleShader = RippleShader()
     private val ripplePaint = Paint()
+    private var unlockedRippleAnimator: AnimatorSet? = null
     private var fadeDwellAnimator: Animator? = null
     private var retractDwellAnimator: Animator? = null
     private var dwellPulseOutAnimator: Animator? = null
@@ -86,7 +86,7 @@
 
     init {
         rippleShader.color = 0xffffffff.toInt() // default color
-        rippleShader.progress = 0f
+        rippleShader.rawProgress = 0f
         rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
         ripplePaint.shader = rippleShader
 
@@ -205,7 +205,7 @@
      * Plays a ripple animation that grows to the dwellRadius with distortion.
      */
     fun startDwellRipple(isDozing: Boolean) {
-        if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) {
+        if (unlockedRippleAnimator?.isRunning == true || dwellPulseOutAnimator?.isRunning == true) {
             return
         }
 
@@ -262,16 +262,14 @@
      * Ripple that bursts outwards from the position of the sensor to the edges of the screen
      */
     fun startUnlockedRipple(onAnimationEnd: Runnable?) {
-        if (unlockedRippleInProgress) {
-            return // Ignore if ripple effect is already playing
-        }
+        unlockedRippleAnimator?.cancel()
 
         val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
             interpolator = Interpolators.LINEAR_OUT_SLOW_IN
             duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
             addUpdateListener { animator ->
                 val now = animator.currentPlayTime
-                rippleShader.progress = animator.animatedValue as Float
+                rippleShader.rawProgress = animator.animatedValue as Float
                 rippleShader.time = now.toFloat()
 
                 invalidate()
@@ -289,14 +287,13 @@
             }
         }
 
-        val animatorSet = AnimatorSet().apply {
+        unlockedRippleAnimator = AnimatorSet().apply {
             playTogether(
                 rippleAnimator,
                 alphaInAnimator
             )
             addListener(object : AnimatorListenerAdapter() {
                 override fun onAnimationStart(animation: Animator?) {
-                    unlockedRippleInProgress = true
                     rippleShader.rippleFill = false
                     drawRipple = true
                     visibility = VISIBLE
@@ -304,13 +301,13 @@
 
                 override fun onAnimationEnd(animation: Animator?) {
                     onAnimationEnd?.run()
-                    unlockedRippleInProgress = false
                     drawRipple = false
                     visibility = GONE
+                    unlockedRippleAnimator = null
                 }
             })
         }
-        animatorSet.start()
+        unlockedRippleAnimator?.start()
     }
 
     fun resetRippleAlpha() {
@@ -345,7 +342,7 @@
     override fun onDraw(canvas: Canvas?) {
         // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
         // the active effect area. Values here should be kept in sync with the
-        // animation implementation in the ripple shader.
+        // animation implementation in the ripple shader. (Twice bigger)
         if (drawDwell) {
             val maskRadius = (1 - (1 - dwellShader.progress) * (1 - dwellShader.progress) *
                     (1 - dwellShader.progress)) * dwellRadius * 2f
@@ -354,10 +351,8 @@
         }
 
         if (drawRipple) {
-            val mask = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
-                    (1 - rippleShader.progress)) * radius * 2f
             canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(),
-                    mask, ripplePaint)
+                    rippleShader.currentWidth, ripplePaint)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 1afa9b2..c799e91 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -54,12 +54,18 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.util.concurrency.DelayableExecutor
 import java.io.PrintWriter
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
 
 private const val TAG = "SideFpsController"
 
@@ -79,6 +85,9 @@
     displayManager: DisplayManager,
     @Main private val mainExecutor: DelayableExecutor,
     @Main private val handler: Handler,
+    private val alternateBouncerInteractor: AlternateBouncerInteractor,
+    @Application private val scope: CoroutineScope,
+    private val featureFlags: FeatureFlags,
     dumpManager: DumpManager
 ) : Dumpable {
     val requests: HashSet<SideFpsUiRequestSource> = HashSet()
@@ -111,7 +120,7 @@
         context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
 
     private val isReverseDefaultRotation =
-        context.getResources().getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
+        context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
 
     private var overlayHideAnimator: ViewPropertyAnimator? = null
 
@@ -168,9 +177,26 @@
             }
         )
         overviewProxyService.addCallback(overviewProxyListener)
+        listenForAlternateBouncerVisibility()
+
         dumpManager.registerDumpable(this)
     }
 
+    private fun listenForAlternateBouncerVisibility() {
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+        if (featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)) {
+            scope.launch {
+                alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
+                    if (isVisible) {
+                        show(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
+                    } else {
+                        hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
+                    }
+                }
+            }
+        }
+    }
+
     /** Shows the side fps overlay if not already shown. */
     fun show(request: SideFpsUiRequestSource) {
         requests.add(request)
@@ -268,10 +294,12 @@
         val isDefaultOrientation =
             if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
         val size = windowManager.maximumWindowMetrics.bounds
+
         val displayWidth = if (isDefaultOrientation) size.width() else size.height()
         val displayHeight = if (isDefaultOrientation) size.height() else size.width()
         val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
         val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
+
         val sensorBounds =
             if (overlayOffsets.isYAligned()) {
                 Rect(
@@ -297,6 +325,7 @@
 
         overlayViewParams.x = sensorBounds.left
         overlayViewParams.y = sensorBounds.top
+
         windowManager.updateViewLayout(overlayView, overlayViewParams)
     }
 
@@ -306,7 +335,12 @@
         }
         // hide after a few seconds if the sensor is oriented down and there are
         // large overlapping system bars
-        val rotation = context.display?.rotation
+        var rotation = context.display?.rotation
+
+        if (rotation != null) {
+            rotation = getRotationFromDefault(rotation)
+        }
+
         if (
             windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
                 ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
@@ -415,4 +449,5 @@
     AUTO_SHOW,
     /** Pin, pattern or password bouncer */
     PRIMARY_BOUNCER,
+    ALTERNATE_BOUNCER
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3a9706d..3fd00ae 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -87,6 +87,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
+import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
@@ -151,6 +152,7 @@
     @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Nullable private final TouchProcessor mTouchProcessor;
     @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @NonNull private final SecureSettings mSecureSettings;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
@@ -240,7 +242,7 @@
                             mLockscreenShadeTransitionController, mConfigurationController,
                             mKeyguardStateController,
                             mUnlockedScreenOffAnimationController,
-                            mUdfpsDisplayMode, requestId, reason, callback,
+                            mUdfpsDisplayMode, mSecureSettings, requestId, reason, callback,
                             (view, event, fromUdfpsView) -> onTouch(requestId, event,
                                     fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
                             mPrimaryBouncerInteractor, mAlternateBouncerInteractor)));
@@ -723,7 +725,8 @@
             @NonNull @BiometricsBackground Executor biometricsExecutor,
             @NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
             @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
-            @NonNull AlternateBouncerInteractor alternateBouncerInteractor) {
+            @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
+            @NonNull SecureSettings secureSettings) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -764,6 +767,7 @@
         mBiometricExecutor = biometricsExecutor;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mSecureSettings = secureSettings;
 
         mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
                 ? singlePointerTouchProcessor : null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index a3c4985..b6b5d26 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -60,6 +60,7 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.SecureSettings
 
 private const val TAG = "UdfpsControllerOverlay"
 
@@ -89,6 +90,7 @@
         private val keyguardStateController: KeyguardStateController,
         private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
         private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+        private val secureSettings: SecureSettings,
         val requestId: Long,
         @ShowReason val requestReason: Int,
         private val controllerCallback: IUdfpsOverlayControllerCallback,
@@ -131,7 +133,7 @@
     /** A helper if the [requestReason] was due to enrollment. */
     val enrollHelper: UdfpsEnrollHelper? =
         if (requestReason.isEnrollmentReason() && !shouldRemoveEnrollmentUi()) {
-            UdfpsEnrollHelper(context, fingerprintManager, requestReason)
+            UdfpsEnrollHelper(context, fingerprintManager, secureSettings, requestReason)
         } else {
             null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index d5c763d3..cfa8ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -24,11 +24,12 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Build;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.systemui.util.settings.SecureSettings;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -51,8 +52,8 @@
         void onLastStepAcquired();
     }
 
-    @NonNull private final Context mContext;
     @NonNull private final FingerprintManager mFingerprintManager;
+    @NonNull private final SecureSettings mSecureSettings;
     // IUdfpsOverlayController reason
     private final int mEnrollReason;
     private final boolean mAccessibilityEnabled;
@@ -70,10 +71,11 @@
     @Nullable Listener mListener;
 
     public UdfpsEnrollHelper(@NonNull Context context,
-            @NonNull FingerprintManager fingerprintManager, int reason) {
+            @NonNull FingerprintManager fingerprintManager, SecureSettings secureSettings,
+            int reason) {
 
-        mContext = context;
         mFingerprintManager = fingerprintManager;
+        mSecureSettings = secureSettings;
         mEnrollReason = reason;
 
         final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
@@ -84,8 +86,7 @@
         // Number of pixels per mm
         float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
                 context.getResources().getDisplayMetrics());
-        boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                NEW_COORDS_OVERRIDE, 0,
+        boolean useNewCoords = mSecureSettings.getIntForUser(NEW_COORDS_OVERRIDE, 0,
                 UserHandle.USER_CURRENT) != 0;
         if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
             Log.v(TAG, "Using new coordinates");
@@ -210,8 +211,7 @@
 
         float scale = SCALE;
         if (Build.IS_ENG || Build.IS_USERDEBUG) {
-            scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
-                    SCALE_OVERRIDE, SCALE,
+            scale = mSecureSettings.getFloatForUser(SCALE_OVERRIDE, SCALE,
                     UserHandle.USER_CURRENT);
         }
         final int index = mLocationsEnrolled - mCenterTouchCount;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index cee2282..addbee9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionListener
@@ -41,7 +40,6 @@
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
-import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
@@ -84,7 +82,6 @@
     ) {
     private val useExpandedOverlay: Boolean =
         featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
-    private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
     private val isModernAlternateBouncerEnabled: Boolean =
         featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
     private var showingUdfpsBouncer = false
@@ -110,12 +107,6 @@
                 )
             }
         }
-    /**
-     * Hidden amount of input (pin/pattern/password) bouncer. This is used
-     * [KeyguardBouncerConstants.EXPANSION_VISIBLE] (0f) to
-     * [KeyguardBouncerConstants.EXPANSION_HIDDEN] (1f). Only used for the non-modernBouncer.
-     */
-    private var inputBouncerHiddenAmount = KeyguardBouncerConstants.EXPANSION_HIDDEN
     private var inputBouncerExpansion = 0f // only used for modernBouncer
 
     private val stateListener: StatusBarStateController.StateListener =
@@ -149,21 +140,6 @@
             }
         }
 
-    private val mPrimaryBouncerExpansionCallback: PrimaryBouncerExpansionCallback =
-        object : PrimaryBouncerExpansionCallback {
-            override fun onExpansionChanged(expansion: Float) {
-                inputBouncerHiddenAmount = expansion
-                updateAlpha()
-                updatePauseAuth()
-            }
-
-            override fun onVisibilityChanged(isVisible: Boolean) {
-                updateBouncerHiddenAmount()
-                updateAlpha()
-                updatePauseAuth()
-            }
-        }
-
     private val configurationListener: ConfigurationController.ConfigurationListener =
         object : ConfigurationController.ConfigurationListener {
             override fun onUiModeChanged() {
@@ -269,15 +245,13 @@
     }
 
     init {
-        if (isModernBouncerEnabled || isModernAlternateBouncerEnabled) {
-            view.repeatWhenAttached {
-                // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
-                // can make the view not visible; and we still want to listen for events
-                // that may make the view visible again.
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    if (isModernBouncerEnabled) listenForBouncerExpansion(this)
-                    if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
-                }
+        view.repeatWhenAttached {
+            // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+            // can make the view not visible; and we still want to listen for events
+            // that may make the view visible again.
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                listenForBouncerExpansion(this)
+                if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
             }
         }
     }
@@ -315,14 +289,6 @@
         statusBarState = statusBarStateController.state
         qsExpansion = keyguardViewManager.qsExpansion
         keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
-        if (!isModernBouncerEnabled) {
-            val bouncer = keyguardViewManager.primaryBouncer
-            bouncer?.expansion?.let {
-                mPrimaryBouncerExpansionCallback.onExpansionChanged(it)
-                bouncer.addBouncerExpansionCallback(mPrimaryBouncerExpansionCallback)
-            }
-            updateBouncerHiddenAmount()
-        }
         configurationController.addCallback(configurationListener)
         shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
         updateScaleFactor()
@@ -352,16 +318,10 @@
         }
         activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
         keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
-        if (!isModernBouncerEnabled) {
-            keyguardViewManager.primaryBouncer?.removeBouncerExpansionCallback(
-                mPrimaryBouncerExpansionCallback
-            )
-        }
     }
 
     override fun dump(pw: PrintWriter, args: Array<String>) {
         super.dump(pw, args)
-        pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
         pw.println("isModernAlternateBouncerEnabled=$isModernAlternateBouncerEnabled")
         pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
         pw.println(
@@ -381,11 +341,7 @@
         pw.println("udfpsRequestedByApp=$udfpsRequested")
         pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
         pw.println("lastDozeAmount=$lastDozeAmount")
-        if (isModernBouncerEnabled) {
-            pw.println("inputBouncerExpansion=$inputBouncerExpansion")
-        } else {
-            pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount")
-        }
+        pw.println("inputBouncerExpansion=$inputBouncerExpansion")
         view.dump(pw)
     }
 
@@ -412,7 +368,6 @@
         } else {
             keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
         }
-        updateBouncerHiddenAmount()
         updateAlpha()
         updatePauseAuth()
         return true
@@ -453,19 +408,11 @@
     }
 
     fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
-        return if (isModernBouncerEnabled) {
-            inputBouncerExpansion >= bouncerExpansionThreshold
-        } else {
-            inputBouncerHiddenAmount < bouncerExpansionThreshold
-        }
+        return inputBouncerExpansion >= bouncerExpansionThreshold
     }
 
     fun isInputBouncerFullyVisible(): Boolean {
-        return if (isModernBouncerEnabled) {
-            inputBouncerExpansion == 1f
-        } else {
-            keyguardViewManager.isBouncerShowing && !alternateBouncerInteractor.isVisibleState()
-        }
+        return inputBouncerExpansion == 1f
     }
 
     override fun listenForTouchesOutsideView(): Boolean {
@@ -517,11 +464,7 @@
     }
 
     private fun getInputBouncerHiddenAmt(): Float {
-        return if (isModernBouncerEnabled) {
-            1f - inputBouncerExpansion
-        } else {
-            inputBouncerHiddenAmount
-        }
+        return 1f - inputBouncerExpansion
     }
 
     /** Update the scale factor based on the device's resolution. */
@@ -529,19 +472,6 @@
         udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
     }
 
-    private fun updateBouncerHiddenAmount() {
-        if (isModernBouncerEnabled) {
-            return
-        }
-        val altBouncerShowing = alternateBouncerInteractor.isVisibleState()
-        if (altBouncerShowing || !keyguardViewManager.primaryBouncerIsOrWillBeShowing()) {
-            inputBouncerHiddenAmount = 1f
-        } else if (keyguardViewManager.isBouncerShowing) {
-            // input bouncer is fully showing
-            inputBouncerHiddenAmount = 0f
-        }
-    }
-
     private val legacyAlternateBouncer: LegacyAlternateBouncer =
         object : LegacyAlternateBouncer {
             override fun showAlternateBouncer(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index 3a01cd5..39ea936 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -54,9 +54,12 @@
         return when (event.actionMasked) {
             MotionEvent.ACTION_DOWN,
             MotionEvent.ACTION_POINTER_DOWN,
-            MotionEvent.ACTION_MOVE -> processActionMove(preprocess())
+            MotionEvent.ACTION_MOVE,
+            MotionEvent.ACTION_HOVER_ENTER,
+            MotionEvent.ACTION_HOVER_MOVE -> processActionMove(preprocess())
             MotionEvent.ACTION_UP,
-            MotionEvent.ACTION_POINTER_UP ->
+            MotionEvent.ACTION_POINTER_UP,
+            MotionEvent.ACTION_HOVER_EXIT ->
                 processActionUp(preprocess(), event.getPointerId(event.actionIndex))
             MotionEvent.ACTION_CANCEL -> processActionCancel(NormalizedTouchData())
             else ->
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index 58d40d3..4227a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -25,13 +25,13 @@
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.os.RemoteException
-import android.os.UserHandle
 import android.util.Log
 import android.view.WindowManager
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -55,6 +55,7 @@
     private val cameraIntents: CameraIntentsWrapper,
     private val contentResolver: ContentResolver,
     @Main private val uiExecutor: Executor,
+    private val userTracker: UserTracker
 ) {
     /**
      * Whether the camera application can be launched for the camera launch gesture.
@@ -111,7 +112,7 @@
                         Intent.FLAG_ACTIVITY_NEW_TASK,
                         null,
                         activityOptions.toBundle(),
-                        UserHandle.CURRENT.identifier,
+                        userTracker.userId,
                     )
                 } catch (e: RemoteException) {
                     Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index 867faf9..cc43e7e 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -20,15 +20,13 @@
 import android.content.Intent
 import android.provider.MediaStore
 import android.text.TextUtils
-
 import com.android.systemui.R
 
 class CameraIntents {
     companion object {
-        val DEFAULT_SECURE_CAMERA_INTENT_ACTION =
-                MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
-        val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
-                MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+        val DEFAULT_SECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
+        val DEFAULT_INSECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+        private val VIDEO_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_VIDEO_CAMERA
         const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
 
         @JvmStatic
@@ -44,18 +42,14 @@
         @JvmStatic
         fun getInsecureCameraIntent(context: Context): Intent {
             val intent = Intent(DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
-            getOverrideCameraPackage(context)?.let {
-                intent.setPackage(it)
-            }
+            getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
             return intent
         }
 
         @JvmStatic
         fun getSecureCameraIntent(context: Context): Intent {
             val intent = Intent(DEFAULT_SECURE_CAMERA_INTENT_ACTION)
-            getOverrideCameraPackage(context)?.let {
-                intent.setPackage(it)
-            }
+            getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
             return intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
         }
 
@@ -68,5 +62,11 @@
         fun isInsecureCameraIntent(intent: Intent?): Boolean {
             return intent?.getAction()?.equals(DEFAULT_INSECURE_CAMERA_INTENT_ACTION) ?: false
         }
+
+        /** Returns an [Intent] that can be used to start the camera in video mode. */
+        @JvmStatic
+        fun getVideoCameraIntent(): Intent {
+            return Intent(VIDEO_CAMERA_INTENT_ACTION)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
index cf02f8f..a434617 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -21,7 +21,9 @@
 import javax.inject.Inject
 
 /** Injectable wrapper around [CameraIntents]. */
-class CameraIntentsWrapper @Inject constructor(
+class CameraIntentsWrapper
+@Inject
+constructor(
     private val context: Context,
 ) {
 
@@ -40,4 +42,9 @@
     fun getInsecureCameraIntent(): Intent {
         return CameraIntents.getInsecureCameraIntent(context)
     }
+
+    /** Returns an [Intent] that can be used to start the camera in video mode. */
+    fun getVideoCameraIntent(): Intent {
+        return CameraIntents.getVideoCameraIntent()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 1454210..fb0c0a6 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -20,7 +20,7 @@
 import android.content.res.Configuration
 import android.graphics.PixelFormat
 import android.os.SystemProperties
-import android.util.DisplayMetrics
+import android.view.Surface
 import android.view.View
 import android.view.WindowManager
 import com.android.internal.annotations.VisibleForTesting
@@ -36,7 +36,6 @@
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.leak.RotationUtils
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -172,30 +171,28 @@
     }
 
     private fun layoutRipple() {
-        val displayMetrics = DisplayMetrics()
-        context.display.getRealMetrics(displayMetrics)
-        val width = displayMetrics.widthPixels
-        val height = displayMetrics.heightPixels
+        val bounds = windowManager.currentWindowMetrics.bounds
+        val width = bounds.width()
+        val height = bounds.height()
         val maxDiameter = Integer.max(width, height) * 2f
         rippleView.setMaxSize(maxDiameter, maxDiameter)
-        when (RotationUtils.getExactRotation(context)) {
-            RotationUtils.ROTATION_LANDSCAPE -> {
+        when (context.display.rotation) {
+            Surface.ROTATION_0 -> {
+                rippleView.setCenter(
+                        width * normalizedPortPosX, height * normalizedPortPosY)
+            }
+            Surface.ROTATION_90 -> {
                 rippleView.setCenter(
                         width * normalizedPortPosY, height * (1 - normalizedPortPosX))
             }
-            RotationUtils.ROTATION_UPSIDE_DOWN -> {
+            Surface.ROTATION_180 -> {
                 rippleView.setCenter(
                         width * (1 - normalizedPortPosX), height * (1 - normalizedPortPosY))
             }
-            RotationUtils.ROTATION_SEASCAPE -> {
+            Surface.ROTATION_270 -> {
                 rippleView.setCenter(
                         width * (1 - normalizedPortPosY), height * normalizedPortPosX)
             }
-            else -> {
-                // ROTATION_NONE
-                rippleView.setCenter(
-                        width * normalizedPortPosX, height * normalizedPortPosY)
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 805a20a..82bb723 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -18,10 +18,10 @@
 
 import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
 
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
 
 import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
 
@@ -29,7 +29,6 @@
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.os.SystemProperties;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -38,8 +37,6 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.util.DeviceConfigProxy;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -59,42 +56,31 @@
             "com.android.systemui.SUPPRESS_CLIPBOARD_OVERLAY";
 
     private final Context mContext;
-    private final DeviceConfigProxy mDeviceConfig;
     private final Provider<ClipboardOverlayController> mOverlayProvider;
-    private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
     private final ClipboardToast mClipboardToast;
     private final ClipboardManager mClipboardManager;
-    private final UiEventLogger mUiEventLogger;
     private final FeatureFlags mFeatureFlags;
-    private boolean mUsingNewOverlay;
+    private final UiEventLogger mUiEventLogger;
     private ClipboardOverlay mClipboardOverlay;
 
     @Inject
-    public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy,
+    public ClipboardListener(Context context,
             Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
-            ClipboardOverlayControllerLegacyFactory overlayFactory,
             ClipboardToast clipboardToast,
             ClipboardManager clipboardManager,
-            UiEventLogger uiEventLogger,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            UiEventLogger uiEventLogger) {
         mContext = context;
-        mDeviceConfig = deviceConfigProxy;
         mOverlayProvider = clipboardOverlayControllerProvider;
-        mOverlayFactory = overlayFactory;
         mClipboardToast = clipboardToast;
         mClipboardManager = clipboardManager;
-        mUiEventLogger = uiEventLogger;
         mFeatureFlags = featureFlags;
-
-        mUsingNewOverlay = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
+        mUiEventLogger = uiEventLogger;
     }
 
     @Override
     public void start() {
-        if (mDeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED, true)) {
-            mClipboardManager.addPrimaryClipChangedListener(this);
-        }
+        mClipboardManager.addPrimaryClipChangedListener(this);
     }
 
     @Override
@@ -120,19 +106,17 @@
             return;
         }
 
-        boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
-        if (mClipboardOverlay == null || enabled != mUsingNewOverlay) {
-            mUsingNewOverlay = enabled;
-            if (enabled) {
-                mClipboardOverlay = mOverlayProvider.get();
-            } else {
-                mClipboardOverlay = mOverlayFactory.create(mContext);
-            }
+        if (mClipboardOverlay == null) {
+            mClipboardOverlay = mOverlayProvider.get();
             mUiEventLogger.log(CLIPBOARD_OVERLAY_ENTERED, 0, clipSource);
         } else {
             mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
         }
-        mClipboardOverlay.setClipData(clipData, clipSource);
+        if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+            mClipboardOverlay.setClipData(clipData, clipSource);
+        } else {
+            mClipboardOverlay.setClipDataLegacy(clipData, clipSource);
+        }
         mClipboardOverlay.setOnSessionCompleteListener(() -> {
             // Session is complete, free memory until it's needed again.
             mClipboardOverlay = null;
@@ -175,6 +159,8 @@
     }
 
     interface ClipboardOverlay {
+        void setClipDataLegacy(ClipData clipData, String clipSource);
+
         void setClipData(ClipData clipData, String clipSource);
 
         void setOnSessionCompleteListener(Runnable runnable);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
new file mode 100644
index 0000000..c7aaf09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.systemui.clipboardoverlay
+
+import android.content.ClipData
+import android.content.ClipDescription.EXTRA_IS_SENSITIVE
+import android.content.Context
+import android.graphics.Bitmap
+import android.text.TextUtils
+import android.util.Log
+import android.util.Size
+import com.android.systemui.R
+import java.io.IOException
+
+data class ClipboardModel(
+    val clipData: ClipData?,
+    val source: String,
+    val type: Type = Type.OTHER,
+    val item: ClipData.Item? = null,
+    val isSensitive: Boolean = false,
+    val isRemote: Boolean = false,
+) {
+    private var _bitmap: Bitmap? = null
+
+    fun dataMatches(other: ClipboardModel?): Boolean {
+        if (other == null) {
+            return false
+        }
+        return source == other.source &&
+            type == other.type &&
+            item?.text == other.item?.text &&
+            item?.uri == other.item?.uri &&
+            isSensitive == other.isSensitive
+    }
+
+    fun loadThumbnail(context: Context): Bitmap? {
+        if (_bitmap == null && type == Type.IMAGE && item?.uri != null) {
+            try {
+                val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
+                _bitmap =
+                    context.contentResolver.loadThumbnail(item.uri, Size(size, size * 4), null)
+            } catch (e: IOException) {
+                Log.e(TAG, "Thumbnail loading failed!", e)
+            }
+        }
+        return _bitmap
+    }
+
+    internal companion object {
+        private val TAG: String = "ClipboardModel"
+
+        @JvmStatic
+        fun fromClipData(
+            context: Context,
+            utils: ClipboardOverlayUtils,
+            clipData: ClipData?,
+            source: String
+        ): ClipboardModel {
+            if (clipData == null || clipData.itemCount == 0) {
+                return ClipboardModel(clipData, source)
+            }
+            val sensitive = clipData.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
+            val item = clipData.getItemAt(0)!!
+            val type = getType(context, item)
+            val remote = utils.isRemoteCopy(context, clipData, source)
+            return ClipboardModel(clipData, source, type, item, sensitive, remote)
+        }
+
+        private fun getType(context: Context, item: ClipData.Item): Type {
+            return if (!TextUtils.isEmpty(item.text)) {
+                Type.TEXT
+            } else if (
+                item.uri != null &&
+                    context.contentResolver.getType(item.uri)?.startsWith("image") == true
+            ) {
+                Type.IMAGE
+            } else {
+                Type.OTHER
+            }
+        }
+    }
+
+    enum class Type {
+        TEXT,
+        IMAGE,
+        OTHER
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index f97d6af..b41f308 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -17,8 +17,6 @@
 package com.android.systemui.clipboardoverlay;
 
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
@@ -32,10 +30,9 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
 import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
 
-import static java.util.Objects.requireNonNull;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.app.RemoteAction;
@@ -48,7 +45,6 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
-import android.hardware.display.DisplayManager;
 import android.hardware.input.InputManager;
 import android.net.Uri;
 import android.os.Looper;
@@ -56,14 +52,15 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Size;
-import android.view.Display;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.InputMonitor;
 import android.view.MotionEvent;
+import android.view.WindowInsets;
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -95,7 +92,6 @@
     private final Context mContext;
     private final ClipboardLogger mClipboardLogger;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final DisplayManager mDisplayManager;
     private final ClipboardOverlayWindow mWindow;
     private final TimeoutHandler mTimeoutHandler;
     private final ClipboardOverlayUtils mClipboardUtils;
@@ -121,6 +117,9 @@
 
     private Runnable mOnUiUpdate;
 
+    private boolean mIsMinimized;
+    private ClipboardModel mClipboardModel;
+
     private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
             new ClipboardOverlayView.ClipboardOverlayCallbacks() {
                 @Override
@@ -174,6 +173,13 @@
                     mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
                     animateOut();
                 }
+
+                @Override
+                public void onMinimizedViewTapped() {
+                    if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+                        animateFromMinimized();
+                    }
+                }
             };
 
     @Inject
@@ -187,30 +193,27 @@
             ClipboardOverlayUtils clipboardUtils,
             @Background Executor bgExecutor,
             UiEventLogger uiEventLogger) {
+        mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
-        mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
-        final Context displayContext = context.createDisplayContext(getDefaultDisplay());
-        mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
         mView = clipboardOverlayView;
         mWindow = clipboardOverlayWindow;
-        mWindow.init(mView::setInsets, () -> {
+        mWindow.init(this::onInsetsChanged, () -> {
             mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
             hideImmediate();
         });
 
+        mFeatureFlags = featureFlags;
         mTimeoutHandler = timeoutHandler;
         mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
 
-        mFeatureFlags = featureFlags;
         mClipboardUtils = clipboardUtils;
         mBgExecutor = bgExecutor;
 
         mView.setCallbacks(mClipboardCallbacks);
 
-
         mWindow.withWindowAttached(() -> {
             mWindow.setContentView(mView);
             mView.setInsets(mWindow.getWindowInsets(),
@@ -255,8 +258,135 @@
         broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
     }
 
+    @VisibleForTesting
+    void onInsetsChanged(WindowInsets insets, int orientation) {
+        mView.setInsets(insets, orientation);
+        if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
+            if (shouldShowMinimized(insets) && !mIsMinimized) {
+                mIsMinimized = true;
+                mView.setMinimized(true);
+            }
+        }
+    }
+
     @Override // ClipboardListener.ClipboardOverlay
-    public void setClipData(ClipData clipData, String clipSource) {
+    public void setClipData(ClipData data, String source) {
+        ClipboardModel model = ClipboardModel.fromClipData(mContext, mClipboardUtils, data, source);
+        if (mExitAnimator != null && mExitAnimator.isRunning()) {
+            mExitAnimator.cancel();
+        }
+        boolean shouldAnimate = !model.dataMatches(mClipboardModel);
+        mClipboardModel = model;
+        mClipboardLogger.setClipSource(mClipboardModel.getSource());
+        if (shouldAnimate) {
+            reset();
+            mClipboardLogger.setClipSource(mClipboardModel.getSource());
+            if (shouldShowMinimized(mWindow.getWindowInsets())) {
+                mIsMinimized = true;
+                mView.setMinimized(true);
+            } else {
+                setExpandedView();
+            }
+            animateIn();
+            mView.announceForAccessibility(getAccessibilityAnnouncement(mClipboardModel.getType()));
+        } else if (!mIsMinimized) {
+            setExpandedView();
+        }
+        if (mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && mClipboardModel.isRemote()) {
+            mTimeoutHandler.cancelTimeout();
+            mOnUiUpdate = null;
+        } else {
+            mOnUiUpdate = mTimeoutHandler::resetTimeout;
+            mOnUiUpdate.run();
+        }
+    }
+
+    private void setExpandedView() {
+        final ClipboardModel model = mClipboardModel;
+        mView.setMinimized(false);
+        switch (model.getType()) {
+            case TEXT:
+                if ((mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && model.isRemote())
+                        || DeviceConfig.getBoolean(
+                        DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
+                    if (model.getItem().getTextLinks() != null) {
+                        classifyText(model);
+                    }
+                }
+                if (model.isSensitive()) {
+                    mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
+                } else {
+                    mView.showTextPreview(model.getItem().getText(), false);
+                }
+                mView.setEditAccessibilityAction(true);
+                mOnPreviewTapped = this::editText;
+                break;
+            case IMAGE:
+                if (model.isSensitive() || model.loadThumbnail(mContext) != null) {
+                    mView.showImagePreview(
+                            model.isSensitive() ? null : model.loadThumbnail(mContext));
+                    mView.setEditAccessibilityAction(true);
+                    mOnPreviewTapped = () -> editImage(model.getItem().getUri());
+                } else {
+                    // image loading failed
+                    mView.showDefaultTextPreview();
+                }
+                break;
+            case OTHER:
+                mView.showDefaultTextPreview();
+                break;
+        }
+        if (mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR)) {
+            if (!model.isRemote()) {
+                maybeShowRemoteCopy(model.getClipData());
+            }
+        } else {
+            maybeShowRemoteCopy(model.getClipData());
+        }
+        if (model.getType() != ClipboardModel.Type.OTHER) {
+            mOnShareTapped = () -> shareContent(model.getClipData());
+            mView.showShareChip();
+        }
+    }
+
+    private boolean shouldShowMinimized(WindowInsets insets) {
+        return insets.getInsets(WindowInsets.Type.ime()).bottom > 0;
+    }
+
+    private void animateFromMinimized() {
+        mIsMinimized = false;
+        setExpandedView();
+        animateIn();
+    }
+
+    private String getAccessibilityAnnouncement(ClipboardModel.Type type) {
+        if (type == ClipboardModel.Type.TEXT) {
+            return mContext.getString(R.string.clipboard_text_copied);
+        } else if (type == ClipboardModel.Type.IMAGE) {
+            return mContext.getString(R.string.clipboard_image_copied);
+        } else {
+            return mContext.getString(R.string.clipboard_content_copied);
+        }
+    }
+
+    private void classifyText(ClipboardModel model) {
+        mBgExecutor.execute(() -> {
+            Optional<RemoteAction> remoteAction =
+                    mClipboardUtils.getAction(model.getItem(), model.getSource());
+            if (model.equals(mClipboardModel)) {
+                remoteAction.ifPresent(action -> {
+                    mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
+                    mView.setActionChip(action, () -> {
+                        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
+                        animateOut();
+                    });
+                });
+            }
+        });
+    }
+
+    @Override // ClipboardListener.ClipboardOverlay
+    public void setClipDataLegacy(ClipData clipData, String clipSource) {
         if (mExitAnimator != null && mExitAnimator.isRunning()) {
             mExitAnimator.cancel();
         }
@@ -513,10 +643,6 @@
         mClipboardLogger.reset();
     }
 
-    private Display getDefaultDisplay() {
-        return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
-    }
-
     static class ClipboardLogger {
         private final UiEventLogger mUiEventLogger;
         private String mClipSource;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
deleted file mode 100644
index 3a040829..0000000
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacy.java
+++ /dev/null
@@ -1,963 +0,0 @@
-/*
- * Copyright (C) 2021 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.systemui.clipboardoverlay;
-
-import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
-
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EDIT_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
-import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-
-import static java.util.Objects.requireNonNull;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.MainThread;
-import android.app.ICompatCameraControlCallback;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Insets;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.Icon;
-import android.hardware.display.DisplayManager;
-import android.hardware.input.InputManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Looper;
-import android.provider.DeviceConfig;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.MathUtils;
-import android.util.Size;
-import android.util.TypedValue;
-import android.view.Display;
-import android.view.DisplayCutout;
-import android.view.Gravity;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.InputMonitor;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewRootImpl;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.PathInterpolator;
-import android.view.textclassifier.TextClassification;
-import android.view.textclassifier.TextClassificationManager;
-import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextLinks;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.PhoneWindow;
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.screenshot.DraggableConstraintLayout;
-import com.android.systemui.screenshot.FloatingWindowUtil;
-import com.android.systemui.screenshot.OverlayActionChip;
-import com.android.systemui.screenshot.TimeoutHandler;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Controls state and UI for the overlay that appears when something is added to the clipboard
- */
-public class ClipboardOverlayControllerLegacy implements ClipboardListener.ClipboardOverlay {
-    private static final String TAG = "ClipboardOverlayCtrlr";
-    private static final String REMOTE_COPY_ACTION = "android.intent.action.REMOTE_COPY";
-
-    /** Constants for screenshot/copy deconflicting */
-    public static final String SCREENSHOT_ACTION = "com.android.systemui.SCREENSHOT";
-    public static final String SELF_PERMISSION = "com.android.systemui.permission.SELF";
-    public static final String COPY_OVERLAY_ACTION = "com.android.systemui.COPY";
-
-    private static final String EXTRA_EDIT_SOURCE_CLIPBOARD = "edit_source_clipboard";
-
-    private static final int CLIPBOARD_DEFAULT_TIMEOUT_MILLIS = 6000;
-    private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
-    private static final int FONT_SEARCH_STEP_PX = 4;
-
-    private final Context mContext;
-    private final ClipboardLogger mClipboardLogger;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final DisplayManager mDisplayManager;
-    private final DisplayMetrics mDisplayMetrics;
-    private final WindowManager mWindowManager;
-    private final WindowManager.LayoutParams mWindowLayoutParams;
-    private final PhoneWindow mWindow;
-    private final TimeoutHandler mTimeoutHandler;
-    private final AccessibilityManager mAccessibilityManager;
-    private final TextClassifier mTextClassifier;
-
-    private final DraggableConstraintLayout mView;
-    private final View mClipboardPreview;
-    private final ImageView mImagePreview;
-    private final TextView mTextPreview;
-    private final TextView mHiddenPreview;
-    private final View mPreviewBorder;
-    private final OverlayActionChip mEditChip;
-    private final OverlayActionChip mShareChip;
-    private final OverlayActionChip mRemoteCopyChip;
-    private final View mActionContainerBackground;
-    private final View mDismissButton;
-    private final LinearLayout mActionContainer;
-    private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>();
-
-    private Runnable mOnSessionCompleteListener;
-
-    private InputMonitor mInputMonitor;
-    private InputEventReceiver mInputEventReceiver;
-
-    private BroadcastReceiver mCloseDialogsReceiver;
-    private BroadcastReceiver mScreenshotReceiver;
-
-    private boolean mBlockAttach = false;
-    private Animator mExitAnimator;
-    private Animator mEnterAnimator;
-    private final int mOrientation;
-    private boolean mKeyboardVisible;
-
-
-    public ClipboardOverlayControllerLegacy(Context context,
-            BroadcastDispatcher broadcastDispatcher,
-            BroadcastSender broadcastSender,
-            TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
-        mBroadcastDispatcher = broadcastDispatcher;
-        mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
-        final Context displayContext = context.createDisplayContext(getDefaultDisplay());
-        mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
-
-        mClipboardLogger = new ClipboardLogger(uiEventLogger);
-
-        mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-        mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class))
-                .getTextClassifier();
-
-        mWindowManager = mContext.getSystemService(WindowManager.class);
-
-        mDisplayMetrics = new DisplayMetrics();
-        mContext.getDisplay().getRealMetrics(mDisplayMetrics);
-
-        mTimeoutHandler = timeoutHandler;
-        mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
-
-        // Setup the window that we are going to use
-        mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
-        mWindowLayoutParams.setTitle("ClipboardOverlay");
-
-        mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
-        mWindow.setWindowManager(mWindowManager, null, null);
-
-        setWindowFocusable(false);
-
-        mView = (DraggableConstraintLayout)
-                LayoutInflater.from(mContext).inflate(R.layout.clipboard_overlay_legacy, null);
-        mActionContainerBackground =
-                requireNonNull(mView.findViewById(R.id.actions_container_background));
-        mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
-        mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
-        mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
-        mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
-        mHiddenPreview = requireNonNull(mView.findViewById(R.id.hidden_preview));
-        mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
-        mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
-        mShareChip = requireNonNull(mView.findViewById(R.id.share_chip));
-        mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
-        mEditChip.setAlpha(1);
-        mShareChip.setAlpha(1);
-        mRemoteCopyChip.setAlpha(1);
-        mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
-
-        mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
-        mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
-            @Override
-            public void onInteraction() {
-                mTimeoutHandler.resetTimeout();
-            }
-
-            @Override
-            public void onSwipeDismissInitiated(Animator animator) {
-                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
-                mExitAnimator = animator;
-            }
-
-            @Override
-            public void onDismissComplete() {
-                hideImmediate();
-            }
-        });
-
-        mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> {
-            int availableHeight = mTextPreview.getHeight()
-                    - (mTextPreview.getPaddingTop() + mTextPreview.getPaddingBottom());
-            mTextPreview.setMaxLines(availableHeight / mTextPreview.getLineHeight());
-            return true;
-        });
-
-        mDismissButton.setOnClickListener(view -> {
-            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
-            animateOut();
-        });
-
-        mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
-        mRemoteCopyChip.setIcon(
-                Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
-        mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
-        mOrientation = mContext.getResources().getConfiguration().orientation;
-
-        attachWindow();
-        withWindowAttached(() -> {
-            mWindow.setContentView(mView);
-            WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
-            mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
-            updateInsets(insets);
-            mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
-                    new ViewTreeObserver.OnGlobalLayoutListener() {
-                        @Override
-                        public void onGlobalLayout() {
-                            WindowInsets insets =
-                                    mWindowManager.getCurrentWindowMetrics().getWindowInsets();
-                            boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
-                            if (keyboardVisible != mKeyboardVisible) {
-                                mKeyboardVisible = keyboardVisible;
-                                updateInsets(insets);
-                            }
-                        }
-                    });
-            mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
-                    new ViewRootImpl.ActivityConfigCallback() {
-                        @Override
-                        public void onConfigurationChanged(Configuration overrideConfig,
-                                int newDisplayId) {
-                            if (mContext.getResources().getConfiguration().orientation
-                                    != mOrientation) {
-                                mClipboardLogger.logSessionComplete(
-                                        CLIPBOARD_OVERLAY_DISMISSED_OTHER);
-                                hideImmediate();
-                            }
-                        }
-
-                        @Override
-                        public void requestCompatCameraControl(
-                                boolean showControl, boolean transformationApplied,
-                                ICompatCameraControlCallback callback) {
-                            Log.w(TAG, "unexpected requestCompatCameraControl call");
-                        }
-                    });
-        });
-
-        mTimeoutHandler.setOnTimeoutRunnable(() -> {
-            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT);
-            animateOut();
-        });
-
-        mCloseDialogsReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
-                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
-                    animateOut();
-                }
-            }
-        };
-
-        mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
-                new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
-        mScreenshotReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (SCREENSHOT_ACTION.equals(intent.getAction())) {
-                    mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER);
-                    animateOut();
-                }
-            }
-        };
-
-        mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
-                new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED,
-                SELF_PERMISSION);
-        monitorOutsideTouches();
-
-        Intent copyIntent = new Intent(COPY_OVERLAY_ACTION);
-        // Set package name so the system knows it's safe
-        copyIntent.setPackage(mContext.getPackageName());
-        broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
-    }
-
-    @Override // ClipboardListener.ClipboardOverlay
-    public void setClipData(ClipData clipData, String clipSource) {
-        if (mExitAnimator != null && mExitAnimator.isRunning()) {
-            mExitAnimator.cancel();
-        }
-        reset();
-        String accessibilityAnnouncement;
-
-        boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
-                && clipData.getDescription().getExtras()
-                .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
-        if (clipData == null || clipData.getItemCount() == 0) {
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                    mTextPreview);
-            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
-        } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
-            ClipData.Item item = clipData.getItemAt(0);
-            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                    CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
-                if (item.getTextLinks() != null) {
-                    AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
-                }
-            }
-            if (isSensitive) {
-                showEditableText(
-                        mContext.getResources().getString(R.string.clipboard_asterisks), true);
-            } else {
-                showEditableText(item.getText(), false);
-            }
-            showShareChip(clipData);
-            accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
-        } else if (clipData.getItemAt(0).getUri() != null) {
-            if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
-                showShareChip(clipData);
-                accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
-            } else {
-                accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
-            }
-        } else {
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                    mTextPreview);
-            accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
-        }
-        Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
-        // Only show remote copy if it's available.
-        PackageManager packageManager = mContext.getPackageManager();
-        if (packageManager.resolveActivity(
-                remoteCopyIntent, PackageManager.ResolveInfoFlags.of(0)) != null) {
-            mRemoteCopyChip.setContentDescription(
-                    mContext.getString(R.string.clipboard_send_nearby_description));
-            mRemoteCopyChip.setVisibility(View.VISIBLE);
-            mRemoteCopyChip.setOnClickListener((v) -> {
-                mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED);
-                mContext.startActivity(remoteCopyIntent);
-                animateOut();
-            });
-            mActionContainerBackground.setVisibility(View.VISIBLE);
-        } else {
-            mRemoteCopyChip.setVisibility(View.GONE);
-        }
-        withWindowAttached(() -> {
-            if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
-                mView.post(this::animateIn);
-            }
-            mView.announceForAccessibility(accessibilityAnnouncement);
-        });
-        mTimeoutHandler.resetTimeout();
-    }
-
-    @Override // ClipboardListener.ClipboardOverlay
-    public void setOnSessionCompleteListener(Runnable runnable) {
-        mOnSessionCompleteListener = runnable;
-    }
-
-    private void classifyText(ClipData.Item item, String source) {
-        ArrayList<RemoteAction> actions = new ArrayList<>();
-        for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
-            TextClassification classification = mTextClassifier.classifyText(
-                    item.getText(), link.getStart(), link.getEnd(), null);
-            actions.addAll(classification.getActions());
-        }
-        mView.post(() -> {
-            resetActionChips();
-            if (actions.size() > 0) {
-                mActionContainerBackground.setVisibility(View.VISIBLE);
-                for (RemoteAction action : actions) {
-                    Intent targetIntent = action.getActionIntent().getIntent();
-                    ComponentName component = targetIntent.getComponent();
-                    if (component != null && !TextUtils.equals(source,
-                            component.getPackageName())) {
-                        OverlayActionChip chip = constructActionChip(action);
-                        mActionContainer.addView(chip);
-                        mActionChips.add(chip);
-                        break; // only show at most one action chip
-                    }
-                }
-            }
-        });
-    }
-
-    private void showShareChip(ClipData clip) {
-        mShareChip.setVisibility(View.VISIBLE);
-        mActionContainerBackground.setVisibility(View.VISIBLE);
-        mShareChip.setOnClickListener((v) -> shareContent(clip));
-    }
-
-    private OverlayActionChip constructActionChip(RemoteAction action) {
-        OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate(
-                R.layout.overlay_action_chip, mActionContainer, false);
-        chip.setText(action.getTitle());
-        chip.setContentDescription(action.getTitle());
-        chip.setIcon(action.getIcon(), false);
-        chip.setPendingIntent(action.getActionIntent(), () -> {
-            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
-            animateOut();
-        });
-        chip.setAlpha(1);
-        return chip;
-    }
-
-    private void monitorOutsideTouches() {
-        InputManager inputManager = mContext.getSystemService(InputManager.class);
-        mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
-        mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
-                Looper.getMainLooper()) {
-            @Override
-            public void onInputEvent(InputEvent event) {
-                if (event instanceof MotionEvent) {
-                    MotionEvent motionEvent = (MotionEvent) event;
-                    if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                        Region touchRegion = new Region();
-
-                        final Rect tmpRect = new Rect();
-                        mPreviewBorder.getBoundsOnScreen(tmpRect);
-                        tmpRect.inset(
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
-                                        -SWIPE_PADDING_DP));
-                        touchRegion.op(tmpRect, Region.Op.UNION);
-                        mActionContainerBackground.getBoundsOnScreen(tmpRect);
-                        tmpRect.inset(
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
-                                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics,
-                                        -SWIPE_PADDING_DP));
-                        touchRegion.op(tmpRect, Region.Op.UNION);
-                        mDismissButton.getBoundsOnScreen(tmpRect);
-                        touchRegion.op(tmpRect, Region.Op.UNION);
-                        if (!touchRegion.contains(
-                                (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
-                            mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE);
-                            animateOut();
-                        }
-                    }
-                }
-                finishInputEvent(event, true /* handled */);
-            }
-        };
-    }
-
-    private void editImage(Uri uri) {
-        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
-        mContext.startActivity(IntentCreator.getImageEditIntent(uri, mContext));
-        animateOut();
-    }
-
-    private void editText() {
-        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED);
-        mContext.startActivity(IntentCreator.getTextEditorIntent(mContext));
-        animateOut();
-    }
-
-    private void shareContent(ClipData clip) {
-        mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED);
-        mContext.startActivity(IntentCreator.getShareIntent(clip, mContext));
-        animateOut();
-    }
-
-    private void showSinglePreview(View v) {
-        mTextPreview.setVisibility(View.GONE);
-        mImagePreview.setVisibility(View.GONE);
-        mHiddenPreview.setVisibility(View.GONE);
-        v.setVisibility(View.VISIBLE);
-    }
-
-    private void showTextPreview(CharSequence text, TextView textView) {
-        showSinglePreview(textView);
-        final CharSequence truncatedText = text.subSequence(0, Math.min(500, text.length()));
-        textView.setText(truncatedText);
-        updateTextSize(truncatedText, textView);
-
-        textView.addOnLayoutChangeListener(
-                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                    if (right - left != oldRight - oldLeft) {
-                        updateTextSize(truncatedText, textView);
-                    }
-                });
-        mEditChip.setVisibility(View.GONE);
-    }
-
-    private void updateTextSize(CharSequence text, TextView textView) {
-        Paint paint = new Paint(textView.getPaint());
-        Resources res = textView.getResources();
-        float minFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_min_font);
-        float maxFontSize = res.getDimensionPixelSize(R.dimen.clipboard_overlay_max_font);
-        if (isOneWord(text) && fitsInView(text, textView, paint, minFontSize)) {
-            // If the text is a single word and would fit within the TextView at the min font size,
-            // find the biggest font size that will fit.
-            float fontSizePx = minFontSize;
-            while (fontSizePx + FONT_SEARCH_STEP_PX < maxFontSize
-                    && fitsInView(text, textView, paint, fontSizePx + FONT_SEARCH_STEP_PX)) {
-                fontSizePx += FONT_SEARCH_STEP_PX;
-            }
-            // Need to turn off autosizing, otherwise setTextSize is a no-op.
-            textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
-            // It's possible to hit the max font size and not fill the width, so centering
-            // horizontally looks better in this case.
-            textView.setGravity(Gravity.CENTER);
-            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) fontSizePx);
-        } else {
-            // Otherwise just stick with autosize.
-            textView.setAutoSizeTextTypeUniformWithConfiguration((int) minFontSize,
-                    (int) maxFontSize, FONT_SEARCH_STEP_PX, TypedValue.COMPLEX_UNIT_PX);
-            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
-        }
-    }
-
-    private static boolean fitsInView(CharSequence text, TextView textView, Paint paint,
-            float fontSizePx) {
-        paint.setTextSize(fontSizePx);
-        float size = paint.measureText(text.toString());
-        float availableWidth = textView.getWidth() - textView.getPaddingLeft()
-                - textView.getPaddingRight();
-        return size < availableWidth;
-    }
-
-    private static boolean isOneWord(CharSequence text) {
-        return text.toString().split("\\s+", 2).length == 1;
-    }
-
-    private void showEditableText(CharSequence text, boolean hidden) {
-        TextView textView = hidden ? mHiddenPreview : mTextPreview;
-        showTextPreview(text, textView);
-        View.OnClickListener listener = v -> editText();
-        setAccessibilityActionToEdit(textView);
-        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
-            mEditChip.setVisibility(View.VISIBLE);
-            mActionContainerBackground.setVisibility(View.VISIBLE);
-            mEditChip.setContentDescription(
-                    mContext.getString(R.string.clipboard_edit_text_description));
-            mEditChip.setOnClickListener(listener);
-        }
-        textView.setOnClickListener(listener);
-    }
-
-    private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
-        View.OnClickListener listener = v -> editImage(uri);
-        ContentResolver resolver = mContext.getContentResolver();
-        String mimeType = resolver.getType(uri);
-        boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
-        if (isSensitive) {
-            mHiddenPreview.setText(mContext.getString(R.string.clipboard_text_hidden));
-            showSinglePreview(mHiddenPreview);
-            if (isEditableImage) {
-                mHiddenPreview.setOnClickListener(listener);
-                setAccessibilityActionToEdit(mHiddenPreview);
-            }
-        } else if (isEditableImage) { // if the MIMEtype is image, try to load
-            try {
-                int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
-                // The width of the view is capped, height maintains aspect ratio, so allow it to be
-                // taller if needed.
-                Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
-                showSinglePreview(mImagePreview);
-                mImagePreview.setImageBitmap(thumbnail);
-                mImagePreview.setOnClickListener(listener);
-                setAccessibilityActionToEdit(mImagePreview);
-            } catch (IOException e) {
-                Log.e(TAG, "Thumbnail loading failed", e);
-                showTextPreview(
-                        mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                        mTextPreview);
-                isEditableImage = false;
-            }
-        } else {
-            showTextPreview(
-                    mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
-                    mTextPreview);
-        }
-        if (isEditableImage && DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
-            mEditChip.setVisibility(View.VISIBLE);
-            mActionContainerBackground.setVisibility(View.VISIBLE);
-            mEditChip.setOnClickListener(listener);
-            mEditChip.setContentDescription(
-                    mContext.getString(R.string.clipboard_edit_image_description));
-        }
-        return isEditableImage;
-    }
-
-    private void setAccessibilityActionToEdit(View view) {
-        ViewCompat.replaceAccessibilityAction(view,
-                AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
-                mContext.getString(R.string.clipboard_edit), null);
-    }
-
-    private void animateIn() {
-        if (mAccessibilityManager.isEnabled()) {
-            mDismissButton.setVisibility(View.VISIBLE);
-        }
-        mEnterAnimator = getEnterAnimation();
-        mEnterAnimator.start();
-    }
-
-    private void animateOut() {
-        if (mExitAnimator != null && mExitAnimator.isRunning()) {
-            return;
-        }
-        Animator anim = getExitAnimation();
-        anim.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                super.onAnimationCancel(animation);
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                if (!mCancelled) {
-                    hideImmediate();
-                }
-            }
-        });
-        mExitAnimator = anim;
-        anim.start();
-    }
-
-    private Animator getEnterAnimation() {
-        TimeInterpolator linearInterpolator = new LinearInterpolator();
-        TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
-        AnimatorSet enterAnim = new AnimatorSet();
-
-        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
-        rootAnim.setInterpolator(linearInterpolator);
-        rootAnim.setDuration(66);
-        rootAnim.addUpdateListener(animation -> {
-            mView.setAlpha(animation.getAnimatedFraction());
-        });
-
-        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
-        scaleAnim.setInterpolator(scaleInterpolator);
-        scaleAnim.setDuration(333);
-        scaleAnim.addUpdateListener(animation -> {
-            float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
-            mClipboardPreview.setScaleX(previewScale);
-            mClipboardPreview.setScaleY(previewScale);
-            mPreviewBorder.setScaleX(previewScale);
-            mPreviewBorder.setScaleY(previewScale);
-
-            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
-            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
-            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
-            float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
-            float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
-            mActionContainer.setScaleX(actionsScaleX);
-            mActionContainer.setScaleY(actionsScaleY);
-            mActionContainerBackground.setScaleX(actionsScaleX);
-            mActionContainerBackground.setScaleY(actionsScaleY);
-        });
-
-        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
-        alphaAnim.setInterpolator(linearInterpolator);
-        alphaAnim.setDuration(283);
-        alphaAnim.addUpdateListener(animation -> {
-            float alpha = animation.getAnimatedFraction();
-            mClipboardPreview.setAlpha(alpha);
-            mPreviewBorder.setAlpha(alpha);
-            mDismissButton.setAlpha(alpha);
-            mActionContainer.setAlpha(alpha);
-        });
-
-        mActionContainer.setAlpha(0);
-        mPreviewBorder.setAlpha(0);
-        mClipboardPreview.setAlpha(0);
-        enterAnim.play(rootAnim).with(scaleAnim);
-        enterAnim.play(alphaAnim).after(50).after(rootAnim);
-
-        enterAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                mView.setAlpha(1);
-                mTimeoutHandler.resetTimeout();
-            }
-        });
-        return enterAnim;
-    }
-
-    private Animator getExitAnimation() {
-        TimeInterpolator linearInterpolator = new LinearInterpolator();
-        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
-        AnimatorSet exitAnim = new AnimatorSet();
-
-        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
-        rootAnim.setInterpolator(linearInterpolator);
-        rootAnim.setDuration(100);
-        rootAnim.addUpdateListener(anim -> mView.setAlpha(1 - anim.getAnimatedFraction()));
-
-        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
-        scaleAnim.setInterpolator(scaleInterpolator);
-        scaleAnim.setDuration(250);
-        scaleAnim.addUpdateListener(animation -> {
-            float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
-            mClipboardPreview.setScaleX(previewScale);
-            mClipboardPreview.setScaleY(previewScale);
-            mPreviewBorder.setScaleX(previewScale);
-            mPreviewBorder.setScaleY(previewScale);
-
-            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
-            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
-            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
-            float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
-            float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
-            mActionContainer.setScaleX(actionScaleX);
-            mActionContainer.setScaleY(actionScaleY);
-            mActionContainerBackground.setScaleX(actionScaleX);
-            mActionContainerBackground.setScaleY(actionScaleY);
-        });
-
-        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
-        alphaAnim.setInterpolator(linearInterpolator);
-        alphaAnim.setDuration(166);
-        alphaAnim.addUpdateListener(animation -> {
-            float alpha = 1 - animation.getAnimatedFraction();
-            mClipboardPreview.setAlpha(alpha);
-            mPreviewBorder.setAlpha(alpha);
-            mDismissButton.setAlpha(alpha);
-            mActionContainer.setAlpha(alpha);
-        });
-
-        exitAnim.play(alphaAnim).with(scaleAnim);
-        exitAnim.play(rootAnim).after(150).after(alphaAnim);
-        return exitAnim;
-    }
-
-    private void hideImmediate() {
-        // Note this may be called multiple times if multiple dismissal events happen at the same
-        // time.
-        mTimeoutHandler.cancelTimeout();
-        final View decorView = mWindow.peekDecorView();
-        if (decorView != null && decorView.isAttachedToWindow()) {
-            mWindowManager.removeViewImmediate(decorView);
-        }
-        if (mCloseDialogsReceiver != null) {
-            mBroadcastDispatcher.unregisterReceiver(mCloseDialogsReceiver);
-            mCloseDialogsReceiver = null;
-        }
-        if (mScreenshotReceiver != null) {
-            mBroadcastDispatcher.unregisterReceiver(mScreenshotReceiver);
-            mScreenshotReceiver = null;
-        }
-        if (mInputEventReceiver != null) {
-            mInputEventReceiver.dispose();
-            mInputEventReceiver = null;
-        }
-        if (mInputMonitor != null) {
-            mInputMonitor.dispose();
-            mInputMonitor = null;
-        }
-        if (mOnSessionCompleteListener != null) {
-            mOnSessionCompleteListener.run();
-        }
-    }
-
-    private void resetActionChips() {
-        for (OverlayActionChip chip : mActionChips) {
-            mActionContainer.removeView(chip);
-        }
-        mActionChips.clear();
-    }
-
-    private void reset() {
-        mView.setTranslationX(0);
-        mView.setAlpha(0);
-        mActionContainerBackground.setVisibility(View.GONE);
-        mShareChip.setVisibility(View.GONE);
-        mEditChip.setVisibility(View.GONE);
-        mRemoteCopyChip.setVisibility(View.GONE);
-        resetActionChips();
-        mTimeoutHandler.cancelTimeout();
-        mClipboardLogger.reset();
-    }
-
-    @MainThread
-    private void attachWindow() {
-        View decorView = mWindow.getDecorView();
-        if (decorView.isAttachedToWindow() || mBlockAttach) {
-            return;
-        }
-        mBlockAttach = true;
-        mWindowManager.addView(decorView, mWindowLayoutParams);
-        decorView.requestApplyInsets();
-        mView.requestApplyInsets();
-        decorView.getViewTreeObserver().addOnWindowAttachListener(
-                new ViewTreeObserver.OnWindowAttachListener() {
-                    @Override
-                    public void onWindowAttached() {
-                        mBlockAttach = false;
-                    }
-
-                    @Override
-                    public void onWindowDetached() {
-                    }
-                }
-        );
-    }
-
-    private void withWindowAttached(Runnable action) {
-        View decorView = mWindow.getDecorView();
-        if (decorView.isAttachedToWindow()) {
-            action.run();
-        } else {
-            decorView.getViewTreeObserver().addOnWindowAttachListener(
-                    new ViewTreeObserver.OnWindowAttachListener() {
-                        @Override
-                        public void onWindowAttached() {
-                            mBlockAttach = false;
-                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
-                            action.run();
-                        }
-
-                        @Override
-                        public void onWindowDetached() {
-                        }
-                    });
-        }
-    }
-
-    private void updateInsets(WindowInsets insets) {
-        int orientation = mContext.getResources().getConfiguration().orientation;
-        FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mView.getLayoutParams();
-        if (p == null) {
-            return;
-        }
-        DisplayCutout cutout = insets.getDisplayCutout();
-        Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
-        Insets imeInsets = insets.getInsets(WindowInsets.Type.ime());
-        if (cutout == null) {
-            p.setMargins(0, 0, 0, Math.max(imeInsets.bottom, navBarInsets.bottom));
-        } else {
-            Insets waterfall = cutout.getWaterfallInsets();
-            if (orientation == ORIENTATION_PORTRAIT) {
-                p.setMargins(
-                        waterfall.left,
-                        Math.max(cutout.getSafeInsetTop(), waterfall.top),
-                        waterfall.right,
-                        Math.max(imeInsets.bottom,
-                                Math.max(cutout.getSafeInsetBottom(),
-                                        Math.max(navBarInsets.bottom, waterfall.bottom))));
-            } else {
-                p.setMargins(
-                        waterfall.left,
-                        waterfall.top,
-                        waterfall.right,
-                        Math.max(imeInsets.bottom,
-                                Math.max(navBarInsets.bottom, waterfall.bottom)));
-            }
-        }
-        mView.setLayoutParams(p);
-        mView.requestLayout();
-    }
-
-    private Display getDefaultDisplay() {
-        return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
-    }
-
-    /**
-     * Updates the window focusability.  If the window is already showing, then it updates the
-     * window immediately, otherwise the layout params will be applied when the window is next
-     * shown.
-     */
-    private void setWindowFocusable(boolean focusable) {
-        int flags = mWindowLayoutParams.flags;
-        if (focusable) {
-            mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        } else {
-            mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-        }
-        if (mWindowLayoutParams.flags == flags) {
-            return;
-        }
-        final View decorView = mWindow.peekDecorView();
-        if (decorView != null && decorView.isAttachedToWindow()) {
-            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
-        }
-    }
-
-    static class ClipboardLogger {
-        private final UiEventLogger mUiEventLogger;
-        private boolean mGuarded = false;
-
-        ClipboardLogger(UiEventLogger uiEventLogger) {
-            mUiEventLogger = uiEventLogger;
-        }
-
-        void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
-            if (!mGuarded) {
-                mGuarded = true;
-                mUiEventLogger.log(event);
-            }
-        }
-
-        void reset() {
-            mGuarded = false;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
deleted file mode 100644
index 0d989a7..0000000
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerLegacyFactory.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2022 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.systemui.clipboardoverlay;
-
-import android.content.Context;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.screenshot.TimeoutHandler;
-
-import javax.inject.Inject;
-
-/**
- * A factory that churns out ClipboardOverlayControllerLegacys on demand.
- */
-@SysUISingleton
-public class ClipboardOverlayControllerLegacyFactory {
-
-    private final UiEventLogger mUiEventLogger;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final BroadcastSender mBroadcastSender;
-
-    @Inject
-    public ClipboardOverlayControllerLegacyFactory(BroadcastDispatcher broadcastDispatcher,
-            BroadcastSender broadcastSender, UiEventLogger uiEventLogger) {
-        this.mBroadcastDispatcher = broadcastDispatcher;
-        this.mBroadcastSender = broadcastSender;
-        this.mUiEventLogger = uiEventLogger;
-    }
-
-    /**
-     * One new ClipboardOverlayControllerLegacy, coming right up!
-     */
-    public ClipboardOverlayControllerLegacy create(Context context) {
-        return new ClipboardOverlayControllerLegacy(context, mBroadcastDispatcher, mBroadcastSender,
-                new TimeoutHandler(context), mUiEventLogger);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 2d33157..c9e01ce 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,8 +18,6 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
-import static java.util.Objects.requireNonNull;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -77,6 +75,8 @@
         void onShareButtonTapped();
 
         void onPreviewTapped();
+
+        void onMinimizedViewTapped();
     }
 
     private static final String TAG = "ClipboardView";
@@ -92,6 +92,7 @@
     private ImageView mImagePreview;
     private TextView mTextPreview;
     private TextView mHiddenPreview;
+    private LinearLayout mMinimizedPreview;
     private View mPreviewBorder;
     private OverlayActionChip mEditChip;
     private OverlayActionChip mShareChip;
@@ -117,18 +118,18 @@
 
     @Override
     protected void onFinishInflate() {
-        mActionContainerBackground =
-                requireNonNull(findViewById(R.id.actions_container_background));
-        mActionContainer = requireNonNull(findViewById(R.id.actions));
-        mClipboardPreview = requireNonNull(findViewById(R.id.clipboard_preview));
-        mImagePreview = requireNonNull(findViewById(R.id.image_preview));
-        mTextPreview = requireNonNull(findViewById(R.id.text_preview));
-        mHiddenPreview = requireNonNull(findViewById(R.id.hidden_preview));
-        mPreviewBorder = requireNonNull(findViewById(R.id.preview_border));
-        mEditChip = requireNonNull(findViewById(R.id.edit_chip));
-        mShareChip = requireNonNull(findViewById(R.id.share_chip));
-        mRemoteCopyChip = requireNonNull(findViewById(R.id.remote_copy_chip));
-        mDismissButton = requireNonNull(findViewById(R.id.dismiss_button));
+        mActionContainerBackground = requireViewById(R.id.actions_container_background);
+        mActionContainer = requireViewById(R.id.actions);
+        mClipboardPreview = requireViewById(R.id.clipboard_preview);
+        mPreviewBorder = requireViewById(R.id.preview_border);
+        mImagePreview = requireViewById(R.id.image_preview);
+        mTextPreview = requireViewById(R.id.text_preview);
+        mHiddenPreview = requireViewById(R.id.hidden_preview);
+        mMinimizedPreview = requireViewById(R.id.minimized_preview);
+        mEditChip = requireViewById(R.id.edit_chip);
+        mShareChip = requireViewById(R.id.share_chip);
+        mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
+        mDismissButton = requireViewById(R.id.dismiss_button);
 
         mEditChip.setAlpha(1);
         mShareChip.setAlpha(1);
@@ -163,6 +164,7 @@
         mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
         mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
         mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped());
+        mMinimizedPreview.setOnClickListener(v -> clipboardCallbacks.onMinimizedViewTapped());
     }
 
     void setEditAccessibilityAction(boolean editable) {
@@ -177,12 +179,28 @@
         }
     }
 
+    void setMinimized(boolean minimized) {
+        if (minimized) {
+            mMinimizedPreview.setVisibility(View.VISIBLE);
+            mClipboardPreview.setVisibility(View.GONE);
+            mPreviewBorder.setVisibility(View.GONE);
+            mActionContainer.setVisibility(View.GONE);
+            mActionContainerBackground.setVisibility(View.GONE);
+        } else {
+            mMinimizedPreview.setVisibility(View.GONE);
+            mClipboardPreview.setVisibility(View.VISIBLE);
+            mPreviewBorder.setVisibility(View.VISIBLE);
+            mActionContainer.setVisibility(View.VISIBLE);
+        }
+    }
+
     void setInsets(WindowInsets insets, int orientation) {
         FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) getLayoutParams();
         if (p == null) {
             return;
         }
         Rect margins = computeMargins(insets, orientation);
+
         p.setMargins(margins.left, margins.top, margins.right, margins.bottom);
         setLayoutParams(p);
         requestLayout();
@@ -204,6 +222,12 @@
                 (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
         touchRegion.op(tmpRect, Region.Op.UNION);
 
+        mMinimizedPreview.getBoundsOnScreen(tmpRect);
+        tmpRect.inset(
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP),
+                (int) FloatingWindowUtil.dpToPx(mDisplayMetrics, -SWIPE_PADDING_DP));
+        touchRegion.op(tmpRect, Region.Op.UNION);
+
         mDismissButton.getBoundsOnScreen(tmpRect);
         touchRegion.op(tmpRect, Region.Op.UNION);
 
@@ -298,6 +322,8 @@
         scaleAnim.setDuration(333);
         scaleAnim.addUpdateListener(animation -> {
             float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mMinimizedPreview.setScaleX(previewScale);
+            mMinimizedPreview.setScaleY(previewScale);
             mClipboardPreview.setScaleX(previewScale);
             mClipboardPreview.setScaleY(previewScale);
             mPreviewBorder.setScaleX(previewScale);
@@ -319,12 +345,14 @@
         alphaAnim.setDuration(283);
         alphaAnim.addUpdateListener(animation -> {
             float alpha = animation.getAnimatedFraction();
+            mMinimizedPreview.setAlpha(alpha);
             mClipboardPreview.setAlpha(alpha);
             mPreviewBorder.setAlpha(alpha);
             mDismissButton.setAlpha(alpha);
             mActionContainer.setAlpha(alpha);
         });
 
+        mMinimizedPreview.setAlpha(0);
         mActionContainer.setAlpha(0);
         mPreviewBorder.setAlpha(0);
         mClipboardPreview.setAlpha(0);
@@ -356,6 +384,8 @@
         scaleAnim.setDuration(250);
         scaleAnim.addUpdateListener(animation -> {
             float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mMinimizedPreview.setScaleX(previewScale);
+            mMinimizedPreview.setScaleY(previewScale);
             mClipboardPreview.setScaleX(previewScale);
             mClipboardPreview.setScaleY(previewScale);
             mPreviewBorder.setScaleX(previewScale);
@@ -377,6 +407,7 @@
         alphaAnim.setDuration(166);
         alphaAnim.addUpdateListener(animation -> {
             float alpha = 1 - animation.getAnimatedFraction();
+            mMinimizedPreview.setAlpha(alpha);
             mClipboardPreview.setAlpha(alpha);
             mPreviewBorder.setAlpha(alpha);
             mDismissButton.setAlpha(alpha);
@@ -399,6 +430,7 @@
         mTextPreview.setVisibility(View.GONE);
         mImagePreview.setVisibility(View.GONE);
         mHiddenPreview.setVisibility(View.GONE);
+        mMinimizedPreview.setVisibility(View.GONE);
         v.setVisibility(View.VISIBLE);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
index 2244813..09b2e44 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.clipboardoverlay.dagger;
 
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -28,6 +27,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.clipboardoverlay.ClipboardOverlayView;
+import com.android.systemui.settings.DisplayTracker;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
@@ -46,8 +46,9 @@
      */
     @Provides
     @OverlayWindowContext
-    static Context provideWindowContext(DisplayManager displayManager, Context context) {
-        Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+    static Context provideWindowContext(DisplayManager displayManager,
+            DisplayTracker displayTracker, Context context) {
+        Display display = displayManager.getDisplay(displayTracker.getDefaultDisplayId());
         return context.createWindowContext(display, TYPE_SCREENSHOT, null);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt
new file mode 100644
index 0000000..9763665
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableConstraintLayout.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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.systemui.common.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** A [ConstraintLayout] that also implements [LaunchableView]. */
+open class LaunchableConstraintLayout : ConstraintLayout, LaunchableView {
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int
+    ) : super(context, attrs, defStyleAttr)
+
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+        defStyleRes: Int
+    ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        delegate.setVisibility(visibility)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
index ddde628..2edac52 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.animation.LaunchableViewDelegate
 
 /** A [LinearLayout] that also implements [LaunchableView]. */
-class LaunchableLinearLayout : LinearLayout, LaunchableView {
+open class LaunchableLinearLayout : LinearLayout, LaunchableView {
     private val delegate =
         LaunchableViewDelegate(
             this,
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
new file mode 100644
index 0000000..2dd98dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.systemui.common.ui.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import kotlin.math.pow
+import kotlin.math.sqrt
+import kotlinx.coroutines.DisposableHandle
+
+/**
+ * View designed to handle long-presses.
+ *
+ * The view will not handle any long pressed by default. To set it up, set up a listener and, when
+ * ready to start consuming long-presses, set [setLongPressHandlingEnabled] to `true`.
+ */
+class LongPressHandlingView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    View(
+        context,
+        attrs,
+    ) {
+    interface Listener {
+        /** Notifies that a long-press has been detected by the given view. */
+        fun onLongPressDetected(
+            view: View,
+            x: Int,
+            y: Int,
+        )
+
+        /** Notifies that the gesture was too short for a long press, it is actually a click. */
+        fun onSingleTapDetected(view: View) = Unit
+    }
+
+    var listener: Listener? = null
+
+    private val interactionHandler: LongPressHandlingViewInteractionHandler by lazy {
+        LongPressHandlingViewInteractionHandler(
+            postDelayed = { block, timeoutMs ->
+                val dispatchToken = Any()
+
+                handler.postDelayed(
+                    block,
+                    dispatchToken,
+                    timeoutMs,
+                )
+
+                DisposableHandle { handler.removeCallbacksAndMessages(dispatchToken) }
+            },
+            isAttachedToWindow = ::isAttachedToWindow,
+            onLongPressDetected = { x, y ->
+                listener?.onLongPressDetected(
+                    view = this,
+                    x = x,
+                    y = y,
+                )
+            },
+            onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) },
+        )
+    }
+
+    fun setLongPressHandlingEnabled(isEnabled: Boolean) {
+        interactionHandler.isLongPressHandlingEnabled = isEnabled
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    override fun onTouchEvent(event: MotionEvent?): Boolean {
+        return interactionHandler.onTouchEvent(event?.toModel())
+    }
+}
+
+private fun MotionEvent.toModel(): LongPressHandlingViewInteractionHandler.MotionEventModel {
+    return when (actionMasked) {
+        MotionEvent.ACTION_DOWN ->
+            LongPressHandlingViewInteractionHandler.MotionEventModel.Down(
+                x = x.toInt(),
+                y = y.toInt(),
+            )
+        MotionEvent.ACTION_MOVE ->
+            LongPressHandlingViewInteractionHandler.MotionEventModel.Move(
+                distanceMoved = distanceMoved(),
+            )
+        MotionEvent.ACTION_UP ->
+            LongPressHandlingViewInteractionHandler.MotionEventModel.Up(
+                distanceMoved = distanceMoved(),
+                gestureDuration = gestureDuration(),
+            )
+        MotionEvent.ACTION_CANCEL -> LongPressHandlingViewInteractionHandler.MotionEventModel.Cancel
+        else -> LongPressHandlingViewInteractionHandler.MotionEventModel.Other
+    }
+}
+
+private fun MotionEvent.distanceMoved(): Float {
+    return if (historySize > 0) {
+        sqrt((x - getHistoricalX(0)).pow(2) + (y - getHistoricalY(0)).pow(2))
+    } else {
+        0f
+    }
+}
+
+private fun MotionEvent.gestureDuration(): Long {
+    return eventTime - downTime
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
new file mode 100644
index 0000000..c2d4d12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.systemui.common.ui.view
+
+import android.view.ViewConfiguration
+import kotlinx.coroutines.DisposableHandle
+
+/** Encapsulates logic to handle complex touch interactions with a [LongPressHandlingView]. */
+class LongPressHandlingViewInteractionHandler(
+    /**
+     * Callback to run the given [Runnable] with the given delay, returning a [DisposableHandle]
+     * allowing the delayed runnable to be canceled before it is run.
+     */
+    private val postDelayed: (block: Runnable, delayMs: Long) -> DisposableHandle,
+    /** Callback to be queried to check if the view is attached to its window. */
+    private val isAttachedToWindow: () -> Boolean,
+    /** Callback reporting the a long-press gesture was detected at the given coordinates. */
+    private val onLongPressDetected: (x: Int, y: Int) -> Unit,
+    /** Callback reporting the a single tap gesture was detected at the given coordinates. */
+    private val onSingleTapDetected: () -> Unit,
+) {
+    sealed class MotionEventModel {
+        object Other : MotionEventModel()
+
+        data class Down(
+            val x: Int,
+            val y: Int,
+        ) : MotionEventModel()
+
+        data class Move(
+            val distanceMoved: Float,
+        ) : MotionEventModel()
+
+        data class Up(
+            val distanceMoved: Float,
+            val gestureDuration: Long,
+        ) : MotionEventModel()
+
+        object Cancel : MotionEventModel()
+    }
+
+    var isLongPressHandlingEnabled: Boolean = false
+    var scheduledLongPressHandle: DisposableHandle? = null
+
+    fun onTouchEvent(event: MotionEventModel?): Boolean {
+        if (!isLongPressHandlingEnabled) {
+            return false
+        }
+
+        return when (event) {
+            is MotionEventModel.Down -> {
+                scheduleLongPress(event.x, event.y)
+                true
+            }
+            is MotionEventModel.Move -> {
+                if (event.distanceMoved > ViewConfiguration.getTouchSlop()) {
+                    cancelScheduledLongPress()
+                }
+                false
+            }
+            is MotionEventModel.Up -> {
+                cancelScheduledLongPress()
+                if (
+                    event.distanceMoved <= ViewConfiguration.getTouchSlop() &&
+                        event.gestureDuration < ViewConfiguration.getLongPressTimeout()
+                ) {
+                    dispatchSingleTap()
+                }
+                false
+            }
+            is MotionEventModel.Cancel -> {
+                cancelScheduledLongPress()
+                false
+            }
+            else -> false
+        }
+    }
+
+    private fun scheduleLongPress(
+        x: Int,
+        y: Int,
+    ) {
+        scheduledLongPressHandle =
+            postDelayed(
+                {
+                    dispatchLongPress(
+                        x = x,
+                        y = y,
+                    )
+                },
+                ViewConfiguration.getLongPressTimeout().toLong(),
+            )
+    }
+
+    private fun dispatchLongPress(
+        x: Int,
+        y: Int,
+    ) {
+        if (!isAttachedToWindow()) {
+            return
+        }
+
+        onLongPressDetected(x, y)
+    }
+
+    private fun cancelScheduledLongPress() {
+        scheduledLongPressHandle?.dispose()
+    }
+
+    private fun dispatchSingleTap() {
+        if (!isAttachedToWindow()) {
+            return
+        }
+
+        onSingleTapDetected()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index dbe301d..860149d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -28,12 +28,13 @@
 import android.content.pm.ServiceInfo
 import android.os.UserHandle
 import android.service.controls.ControlsProviderService
+import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
 import com.android.settingslib.applications.DefaultAppInfo
 import com.android.systemui.R
 import java.util.Objects
 
-class ControlsServiceInfo(
+open class ControlsServiceInfo(
     private val context: Context,
     val serviceInfo: ServiceInfo
 ) : DefaultAppInfo(
@@ -64,7 +65,7 @@
      * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
      */
     var panelActivity: ComponentName? = null
-        private set
+        protected set
 
     private var resolved: Boolean = false
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index f29f6d0..822190f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -188,6 +188,8 @@
     /** See [ControlsUiController.getPreferredSelectedItem]. */
     fun getPreferredSelection(): SelectedItem
 
+    fun setPreferredSelection(selectedItem: SelectedItem)
+
     /**
      * Bind to a service that provides a Device Controls panel (embedded activity). This will allow
      * the app to remain "warm", and reduce latency.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 111fcbb..278ee70 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.dagger.SysUISingleton
@@ -61,6 +62,7 @@
     private val listingController: ControlsListingController,
     private val userFileManager: UserFileManager,
     private val userTracker: UserTracker,
+    private val authorizedPanelsRepository: AuthorizedPanelsRepository,
     optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
     dumpManager: DumpManager,
 ) : Dumpable, ControlsController {
@@ -119,16 +121,13 @@
         userChanging = false
     }
 
-    private val userTrackerCallback = object : UserTracker.Callback {
-        override fun onUserChanged(newUser: Int, userContext: Context) {
-            userChanging = true
-            val newUserHandle = UserHandle.of(newUser)
-            if (currentUser == newUserHandle) {
-                userChanging = false
-                return
-            }
-            setValuesForUser(newUserHandle)
+    override fun changeUser(newUser: UserHandle) {
+        userChanging = true
+        if (currentUser == newUser) {
+            userChanging = false
+            return
         }
+        setValuesForUser(newUser)
     }
 
     @VisibleForTesting
@@ -229,7 +228,6 @@
         dumpManager.registerDumpable(javaClass.name, this)
         resetFavorites()
         userChanging = false
-        userTracker.addCallback(userTrackerCallback, executor)
         context.registerReceiver(
             restoreFinishedReceiver,
             IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
@@ -241,7 +239,6 @@
     }
 
     fun destroy() {
-        userTracker.removeCallback(userTrackerCallback)
         context.unregisterReceiver(restoreFinishedReceiver)
         listingController.removeCallback(listingCallback)
     }
@@ -249,6 +246,11 @@
     private fun resetFavorites() {
         Favorites.clear()
         Favorites.load(persistenceWrapper.readFavorites())
+        // After loading favorites, add the package names of any apps with favorites to the list
+        // of authorized panels. That way, if the user has previously favorited controls for an app,
+        // that panel will be authorized.
+        authorizedPanelsRepository.addAuthorizedPanels(
+                Favorites.getAllStructures().map { it.componentName.packageName }.toSet())
     }
 
     private fun confirmAvailability(): Boolean {
@@ -489,6 +491,7 @@
         if (!confirmAvailability()) return
         executor.execute {
             if (Favorites.addFavorite(componentName, structureName, controlInfo)) {
+                authorizedPanelsRepository.addAuthorizedPanels(setOf(componentName.packageName))
                 persistenceWrapper.storeFavorites(Favorites.getAllStructures())
             }
         }
@@ -555,6 +558,10 @@
         return uiController.getPreferredSelectedItem(getFavorites())
     }
 
+    override fun setPreferredSelection(selectedItem: SelectedItem) {
+        uiController.updatePreferences(selectedItem)
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println("ControlsController state:")
         pw.println("  Changing users: $userChanging")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 6d6410d..6af8e73 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -34,6 +34,8 @@
 import com.android.systemui.controls.management.ControlsListingControllerImpl
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.controls.management.ControlsRequestDialog
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.AuthorizedPanelsRepositoryImpl
 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
 import com.android.systemui.controls.ui.ControlActionCoordinator
@@ -104,6 +106,11 @@
         coordinator: ControlActionCoordinatorImpl
     ): ControlActionCoordinator
 
+    @Binds
+    abstract fun provideAuthorizedPanelsRepository(
+        repository: AuthorizedPanelsRepositoryImpl
+    ): AuthorizedPanelsRepository
+
     @BindsOptionalOf
     abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/StartControlsStartableModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/StartControlsStartableModule.kt
new file mode 100644
index 0000000..3f20c26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/StartControlsStartableModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.systemui.controls.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.controls.start.ControlsStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class StartControlsStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(ControlsStartable::class)
+    abstract fun bindFeature(impl: ControlsStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index 753d5ad..3fe0f03 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -45,14 +45,15 @@
  * @param onAppSelected a callback to indicate that an app has been selected in the list.
  */
 class AppAdapter(
-    backgroundExecutor: Executor,
-    uiExecutor: Executor,
-    lifecycle: Lifecycle,
-    controlsListingController: ControlsListingController,
-    private val layoutInflater: LayoutInflater,
-    private val onAppSelected: (ComponentName?) -> Unit = {},
-    private val favoritesRenderer: FavoritesRenderer,
-    private val resources: Resources
+        backgroundExecutor: Executor,
+        uiExecutor: Executor,
+        lifecycle: Lifecycle,
+        controlsListingController: ControlsListingController,
+        private val layoutInflater: LayoutInflater,
+        private val onAppSelected: (ControlsServiceInfo) -> Unit = {},
+        private val favoritesRenderer: FavoritesRenderer,
+        private val resources: Resources,
+        private val authorizedPanels: Set<String> = emptySet(),
 ) : RecyclerView.Adapter<AppAdapter.Holder>() {
 
     private var listOfServices = emptyList<ControlsServiceInfo>()
@@ -64,8 +65,10 @@
                 val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
                     it.loadLabel() ?: ""
                 }
-                listOfServices = serviceInfos.filter { it.panelActivity == null }
-                        .sortedWith(localeComparator)
+                // No panel or the panel is not authorized
+                listOfServices = serviceInfos.filter {
+                    it.panelActivity == null || it.panelActivity?.packageName !in authorizedPanels
+                }.sortedWith(localeComparator)
                 uiExecutor.execute(::notifyDataSetChanged)
             }
         }
@@ -86,8 +89,8 @@
 
     override fun onBindViewHolder(holder: Holder, index: Int) {
         holder.bindData(listOfServices[index])
-        holder.itemView.setOnClickListener {
-            onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key))
+        holder.view.setOnClickListener {
+            onAppSelected(listOfServices[index])
         }
     }
 
@@ -95,6 +98,8 @@
      * Holder for binding views in the [RecyclerView]-
      */
     class Holder(view: View, val favRenderer: FavoritesRenderer) : RecyclerView.ViewHolder(view) {
+        val view: View = itemView
+
         private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon)
         private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
         private val favorites: TextView = itemView.requireViewById(R.id.favorites)
@@ -106,7 +111,11 @@
         fun bindData(data: ControlsServiceInfo) {
             icon.setImageDrawable(data.loadIcon())
             title.text = data.loadLabel()
-            val text = favRenderer.renderFavoritesForComponent(data.componentName)
+            val text = if (data.panelActivity == null) {
+                favRenderer.renderFavoritesForComponent(data.componentName)
+            } else {
+                null
+            }
             favorites.text = text
             favorites.visibility = if (text == null) View.GONE else View.VISIBLE
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 90bc5d0..3808e73 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.controls.management
 
 import android.app.ActivityOptions
+import android.app.Dialog
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
@@ -31,12 +32,15 @@
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import androidx.activity.ComponentActivity
+import androidx.annotation.VisibleForTesting
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
+import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.ui.ControlsActivity
-import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.UserTracker
@@ -52,7 +56,8 @@
     private val listingController: ControlsListingController,
     private val controlsController: ControlsController,
     private val userTracker: UserTracker,
-    private val uiController: ControlsUiController
+    private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+    private val panelConfirmationDialogFactory: PanelConfirmationDialogFactory
 ) : ComponentActivity() {
 
     companion object {
@@ -72,6 +77,7 @@
             }
         }
     }
+    private var dialog: Dialog? = null
 
     private val mOnBackInvokedCallback = OnBackInvokedCallback {
         if (DEBUG) {
@@ -138,9 +144,11 @@
                 lifecycle,
                 listingController,
                 LayoutInflater.from(this),
-                ::launchFavoritingActivity,
+                ::onAppSelected,
                 FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
-                resources).apply {
+                resources,
+                authorizedPanelsRepository.getAuthorizedPanels()
+        ).apply {
             registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
                 var hasAnimated = false
                 override fun onChanged() {
@@ -167,13 +175,35 @@
             Log.d(TAG, "Unregistered onBackInvokedCallback")
         }
         onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
+        dialog?.cancel()
+    }
+
+    fun onAppSelected(serviceInfo: ControlsServiceInfo) {
+        dialog?.cancel()
+        if (serviceInfo.panelActivity == null) {
+            launchFavoritingActivity(serviceInfo.componentName)
+        } else {
+            val appName = serviceInfo.loadLabel() ?: ""
+            dialog = panelConfirmationDialogFactory.createConfirmationDialog(this, appName) { ok ->
+                if (ok) {
+                    authorizedPanelsRepository.addAuthorizedPanels(
+                            setOf(serviceInfo.componentName.packageName)
+                    )
+                    val selected = SelectedItem.PanelItem(appName, componentName)
+                    controlsController.setPreferredSelection(selected)
+                    animateExitAndFinish()
+                    openControlsOrigin()
+                }
+                dialog = null
+            }.also { it.show() }
+        }
     }
 
     /**
      * Launch the [ControlsFavoritingActivity] for the specified component.
      * @param component a component name for a [ControlsProviderService]
      */
-    fun launchFavoritingActivity(component: ComponentName?) {
+    private fun launchFavoritingActivity(component: ComponentName?) {
         executor.execute {
             component?.let {
                 val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
@@ -194,7 +224,15 @@
         super.onDestroy()
     }
 
-    private fun animateExitAndFinish() {
+    private fun openControlsOrigin() {
+        startActivity(
+                Intent(applicationContext, ControlsActivity::class.java),
+                ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
+        )
+    }
+
+    @VisibleForTesting
+    internal open fun animateExitAndFinish() {
         val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
         ControlsAnimations.exitAnimation(
                 rootView,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt
new file mode 100644
index 0000000..6f87aa9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.systemui.controls.management
+
+import android.app.Dialog
+import android.content.Context
+import android.content.DialogInterface
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * Factory to create dialogs for consenting to show app panels for specific apps.
+ *
+ * [internalDialogFactory] is for facilitating testing.
+ */
+class PanelConfirmationDialogFactory(
+    private val internalDialogFactory: (Context) -> SystemUIDialog
+) {
+    @Inject constructor() : this({ SystemUIDialog(it) })
+
+    /**
+     * Creates a dialog to show to the user. [response] will be true if an only if the user responds
+     * affirmatively.
+     */
+    fun createConfirmationDialog(
+        context: Context,
+        appName: CharSequence,
+        response: Consumer<Boolean>
+    ): Dialog {
+        val listener =
+            DialogInterface.OnClickListener { _, which ->
+                response.accept(which == DialogInterface.BUTTON_POSITIVE)
+            }
+        return internalDialogFactory(context).apply {
+            setTitle(this.context.getString(R.string.controls_panel_authorization_title, appName))
+            setMessage(this.context.getString(R.string.controls_panel_authorization, appName))
+            setCanceledOnTouchOutside(true)
+            setOnCancelListener { response.accept(false) }
+            setPositiveButton(R.string.controls_dialog_ok, listener)
+            setNeutralButton(R.string.cancel, listener)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
new file mode 100644
index 0000000..3e672f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.systemui.controls.panels
+
+/**
+ * Repository for keeping track of which packages the panel has authorized to show control panels
+ * (embedded activity).
+ */
+interface AuthorizedPanelsRepository {
+
+    /** A set of package names that the user has previously authorized to show panels. */
+    fun getAuthorizedPanels(): Set<String>
+
+    /** Adds [packageNames] to the set of packages that the user has authorized to show panels. */
+    fun addAuthorizedPanels(packageNames: Set<String>)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
new file mode 100644
index 0000000..f7e43a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.systemui.controls.panels
+
+import android.content.Context
+import android.content.SharedPreferences
+import com.android.systemui.R
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import javax.inject.Inject
+
+class AuthorizedPanelsRepositoryImpl
+@Inject
+constructor(
+    private val context: Context,
+    private val userFileManager: UserFileManager,
+    private val userTracker: UserTracker
+) : AuthorizedPanelsRepository {
+
+    override fun getAuthorizedPanels(): Set<String> {
+        return getAuthorizedPanelsInternal(instantiateSharedPrefs())
+    }
+
+    override fun addAuthorizedPanels(packageNames: Set<String>) {
+        addAuthorizedPanelsInternal(instantiateSharedPrefs(), packageNames)
+    }
+
+    private fun getAuthorizedPanelsInternal(sharedPreferences: SharedPreferences): Set<String> {
+        return sharedPreferences.getStringSet(KEY, emptySet())!!
+    }
+
+    private fun addAuthorizedPanelsInternal(
+        sharedPreferences: SharedPreferences,
+        packageNames: Set<String>
+    ) {
+        val currentSet = getAuthorizedPanelsInternal(sharedPreferences)
+        sharedPreferences.edit().putStringSet(KEY, currentSet + packageNames).apply()
+    }
+
+    private fun instantiateSharedPrefs(): SharedPreferences {
+        val sharedPref =
+            userFileManager.getSharedPreferences(
+                DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                Context.MODE_PRIVATE,
+                userTracker.userId,
+            )
+
+        // If we've never run this (i.e., the key doesn't exist), add the default packages
+        if (sharedPref.getStringSet(KEY, null) == null) {
+            sharedPref
+                .edit()
+                .putStringSet(
+                    KEY,
+                    context.resources
+                        .getStringArray(R.array.config_controlsPreferredPackages)
+                        .toSet()
+                )
+                .apply()
+        }
+        return sharedPref
+    }
+
+    companion object {
+        private const val KEY = "authorized_panels"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
new file mode 100644
index 0000000..9d99253
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.start
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.UserHandle
+import com.android.systemui.CoreStartable
+import com.android.systemui.R
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
+ *
+ * In particular, it will perform the following:
+ * * If there is no preferred selection for provider and at least one of the preferred packages 
+ * provides a panel, it will select the first one that does.
+ * * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
+ * displaying the panel).
+ *
+ * It will also perform those operations on user change.
+ */
+@SysUISingleton
+class ControlsStartable
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    @Background private val executor: Executor,
+    private val controlsComponent: ControlsComponent,
+    private val userTracker: UserTracker
+) : CoreStartable {
+
+    // These two controllers can only be accessed after `start` method once we've checked if the
+    // feature is enabled
+    private val controlsController: ControlsController
+        get() = controlsComponent.getControlsController().get()
+
+    private val controlsListingController: ControlsListingController
+        get() = controlsComponent.getControlsListingController().get()
+
+    private val userTrackerCallback =
+        object : UserTracker.Callback {
+            override fun onUserChanged(newUser: Int, userContext: Context) {
+                controlsController.changeUser(UserHandle.of(newUser))
+                startForUser()
+            }
+        }
+
+    override fun start() {
+        if (!controlsComponent.isEnabled()) {
+            // Controls is disabled, we don't need this anymore
+            return
+        }
+        startForUser()
+        userTracker.addCallback(userTrackerCallback, executor)
+    }
+
+    private fun startForUser() {
+        selectDefaultPanelIfNecessary()
+        bindToPanel()
+    }
+
+    private fun selectDefaultPanelIfNecessary() {
+        val currentSelection = controlsController.getPreferredSelection()
+        if (currentSelection == SelectedItem.EMPTY_SELECTION) {
+            val availableServices = controlsListingController.getCurrentServices()
+            val panels = availableServices.filter { it.panelActivity != null }
+            resources
+                .getStringArray(R.array.config_controlsPreferredPackages)
+                // Looking for the first element in the string array such that there is one package
+                // that has a panel. It will return null if there are no packages in the array,
+                // or if no packages in the array have a panel associated with it.
+                .firstNotNullOfOrNull { name ->
+                    panels.firstOrNull { it.componentName.packageName == name }
+                }
+                ?.let { info ->
+                    controlsController.setPreferredSelection(
+                        SelectedItem.PanelItem(info.loadLabel(), info.componentName)
+                    )
+                }
+        }
+    }
+
+    private fun bindToPanel() {
+        val currentSelection = controlsController.getPreferredSelection()
+        val panels =
+            controlsListingController.getCurrentServices().filter { it.panelActivity != null }
+        if (
+            currentSelection is SelectedItem.PanelItem &&
+                panels.firstOrNull { it.componentName == currentSelection.componentName } != null
+        ) {
+            controlsController.bindComponentForPanel(currentSelection.componentName)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index f5c5905..c1cec9d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -58,6 +58,8 @@
      * This element will be the one that appears when the user first opens the controls activity.
      */
     fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
+
+    fun updatePreferences(selectedItem: SelectedItem)
 }
 
 sealed class SelectedItem {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 6289788..58f4835 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -25,8 +25,10 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
+import android.os.Trace
 import android.service.controls.Control
 import android.service.controls.ControlsProviderService
 import android.util.Log
@@ -38,6 +40,7 @@
 import android.view.animation.DecelerateInterpolator
 import android.widget.AdapterView
 import android.widget.ArrayAdapter
+import android.widget.BaseAdapter
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
@@ -60,10 +63,13 @@
 import com.android.systemui.controls.management.ControlsFavoritingActivity
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.globalactions.GlobalActionsPopupMenu
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserFileManager
@@ -87,6 +93,7 @@
 class ControlsUiControllerImpl @Inject constructor (
         val controlsController: Lazy<ControlsController>,
         val context: Context,
+        private val packageManager: PackageManager,
         @Main val uiExecutor: DelayableExecutor,
         @Background val bgExecutor: DelayableExecutor,
         val controlsListingController: Lazy<ControlsListingController>,
@@ -99,6 +106,8 @@
         private val userTracker: UserTracker,
         private val taskViewFactory: Optional<TaskViewFactory>,
         private val controlsSettingsRepository: ControlsSettingsRepository,
+        private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+        private val featureFlags: FeatureFlags,
         dumpManager: DumpManager
 ) : ControlsUiController, Dumpable {
 
@@ -108,6 +117,11 @@
         private const val PREF_IS_PANEL = "controls_is_panel"
 
         private const val FADE_IN_MILLIS = 200L
+
+        private const val OPEN_APP_ID = 0L
+        private const val ADD_CONTROLS_ID = 1L
+        private const val ADD_APP_ID = 2L
+        private const val EDIT_CONTROLS_ID = 3L
     }
 
     private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
@@ -135,6 +149,9 @@
         it.getTitle()
     }
 
+    private var openAppIntent: Intent? = null
+    private var overflowMenuAdapter: BaseAdapter? = null
+
     private val onSeedingComplete = Consumer<Boolean> {
         accepted ->
             if (accepted) {
@@ -160,6 +177,7 @@
     ): ControlsListingController.ControlsListingCallback {
         return object : ControlsListingController.ControlsListingCallback {
             override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+                val authorizedPanels = authorizedPanelsRepository.getAuthorizedPanels()
                 val lastItems = serviceInfos.map {
                     val uid = it.serviceInfo.applicationInfo.uid
 
@@ -169,7 +187,11 @@
                             it.loadIcon(),
                             it.componentName,
                             uid,
-                            it.panelActivity
+                            if (it.componentName.packageName in authorizedPanels) {
+                                it.panelActivity
+                            } else {
+                                null
+                            }
                     )
                 }
                 uiExecutor.execute {
@@ -203,9 +225,12 @@
         activityContext: Context
     ) {
         Log.d(ControlsUiController.TAG, "show()")
+        Trace.instant(Trace.TRACE_TAG_APP, "ControlsUiControllerImpl#show")
         this.parent = parent
         this.onDismiss = onDismiss
         this.activityContext = activityContext
+        this.openAppIntent = null
+        this.overflowMenuAdapter = null
         hidden = false
         retainCache = false
 
@@ -296,6 +321,12 @@
         startTargetedActivity(si, ControlsEditingActivity::class.java)
     }
 
+    private fun startDefaultActivity() {
+        openAppIntent?.let {
+            startActivity(it, animateExtra = false)
+        }
+    }
+
     private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
         val i = Intent(activityContext, klazz)
         putIntentExtras(i, si)
@@ -319,9 +350,11 @@
         startActivity(i)
     }
 
-    private fun startActivity(intent: Intent) {
+    private fun startActivity(intent: Intent, animateExtra: Boolean = true) {
         // Force animations when transitioning from a dialog to an activity
-        intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+        if (animateExtra) {
+            intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+        }
 
         if (keyguardStateController.isShowing()) {
             activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
@@ -373,8 +406,31 @@
             Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
         }
 
+        bgExecutor.execute {
+            val intent = Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_LAUNCHER)
+                    .setPackage(selectionItem.componentName.packageName)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or
+                            Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+            val intents = packageManager
+                    .queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(0L))
+            intents.firstOrNull { it.activityInfo.exported }?.let { resolved ->
+                intent.setPackage(null)
+                intent.setComponent(resolved.activityInfo.componentName)
+                openAppIntent = intent
+                parent.post {
+                    // This will call show on the PopupWindow in the same thread, so make sure this
+                    // happens in the view thread.
+                    overflowMenuAdapter?.notifyDataSetChanged()
+                }
+            }
+        }
         createDropDown(panelsAndStructures, selectionItem)
-        createMenu()
+
+        val currentApps = panelsAndStructures.map { it.componentName }.toSet()
+        val allApps = controlsListingController.get()
+                .getCurrentServices().map { it.componentName }.toSet()
+        createMenu(extraApps = (allApps - currentApps).isNotEmpty())
     }
 
     private fun createPanelView(componentName: ComponentName) {
@@ -413,22 +469,41 @@
         }
     }
 
-    private fun createMenu() {
+    private fun createMenu(extraApps: Boolean) {
         val isPanel = selectedItem is SelectedItem.PanelItem
         val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
                 ?: EMPTY_STRUCTURE
+        val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
 
-        val items = if (isPanel) {
-            arrayOf(
-                    context.resources.getString(R.string.controls_menu_add),
-            )
-        } else {
-            arrayOf(
-                    context.resources.getString(R.string.controls_menu_add),
-                    context.resources.getString(R.string.controls_menu_edit)
-            )
+        val items = buildList {
+            add(OverflowMenuAdapter.MenuItem(
+                    context.getText(R.string.controls_open_app),
+                    OPEN_APP_ID
+            ))
+            if (newFlows || isPanel) {
+                if (extraApps) {
+                    add(OverflowMenuAdapter.MenuItem(
+                            context.getText(R.string.controls_menu_add_another_app),
+                            ADD_APP_ID
+                    ))
+                }
+            } else {
+                add(OverflowMenuAdapter.MenuItem(
+                        context.getText(R.string.controls_menu_add),
+                        ADD_CONTROLS_ID
+                ))
+            }
+            if (!isPanel) {
+                add(OverflowMenuAdapter.MenuItem(
+                        context.getText(R.string.controls_menu_edit),
+                        EDIT_CONTROLS_ID
+                ))
+            }
         }
-        var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
+
+        val adapter = OverflowMenuAdapter(context, R.layout.controls_more_item, items) { position ->
+                getItemId(position) != OPEN_APP_ID || openAppIntent != null
+        }
 
         val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
         anchor.setOnClickListener(object : View.OnClickListener {
@@ -446,25 +521,21 @@
                             pos: Int,
                             id: Long
                         ) {
-                            when (pos) {
-                                // 0: Add Control
-                                0 -> {
-                                    if (isPanel) {
-                                        startProviderSelectorActivity()
-                                    } else {
-                                        startFavoritingActivity(selectedStructure)
-                                    }
-                                }
-                                // 1: Edit controls
-                                1 -> startEditingActivity(selectedStructure)
+                            when (id) {
+                                OPEN_APP_ID -> startDefaultActivity()
+                                ADD_APP_ID -> startProviderSelectorActivity()
+                                ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
+                                EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
                             }
                             dismiss()
                         }
                     })
                     show()
+                    listView?.post { listView?.requestAccessibilityFocus() }
                 }
             }
         })
+        overflowMenuAdapter = adapter
     }
 
     private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) {
@@ -526,6 +597,7 @@
                         }
                     })
                     show()
+                    listView?.post { listView?.requestAccessibilityFocus() }
                 }
             }
         })
@@ -610,12 +682,12 @@
         }
     }
 
-    private fun updatePreferences(si: SelectedItem) {
+    override fun updatePreferences(selectedItem: SelectedItem) {
         sharedPreferences.edit()
-                .putString(PREF_COMPONENT, si.componentName.flattenToString())
-                .putString(PREF_STRUCTURE_OR_APP_NAME, si.name.toString())
-                .putBoolean(PREF_IS_PANEL, si is SelectedItem.PanelItem)
-                .commit()
+                .putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
+                .putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
+                .putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
+                .apply()
     }
 
     private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/OverflowMenuAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/OverflowMenuAdapter.kt
new file mode 100644
index 0000000..6b84e36
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/OverflowMenuAdapter.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.systemui.controls.ui
+
+import android.content.Context
+import android.widget.ArrayAdapter
+import androidx.annotation.LayoutRes
+
+open class OverflowMenuAdapter(
+    context: Context,
+    @LayoutRes layoutId: Int,
+    itemsWithIds: List<MenuItem>,
+    private val isEnabledInternal: OverflowMenuAdapter.(Int) -> Boolean
+) : ArrayAdapter<CharSequence>(context, layoutId, itemsWithIds.map(MenuItem::text)) {
+
+    private val ids = itemsWithIds.map(MenuItem::id)
+
+    override fun getItemId(position: Int): Long {
+        return ids[position]
+    }
+
+    override fun isEnabled(position: Int): Boolean {
+        return isEnabledInternal(position)
+    }
+
+    data class MenuItem(val text: CharSequence, val id: Long)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index f5764c2..3b6ab20 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -27,6 +27,7 @@
 import android.graphics.Color
 import android.graphics.drawable.ShapeDrawable
 import android.graphics.drawable.shapes.RoundRectShape
+import android.os.Trace
 import com.android.systemui.R
 import com.android.systemui.util.boundsOnScreen
 import com.android.wm.shell.TaskView
@@ -84,6 +85,7 @@
                         options,
                         taskView.boundsOnScreen
                     )
+                    Trace.instant(Trace.TRACE_TAG_APP, "PanelTaskViewController - startActivity")
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 8012dea..cb7c765 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -26,11 +26,15 @@
 import com.android.systemui.accessibility.WindowMagnification
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.controls.dagger.StartControlsStartableModule
 import com.android.systemui.dagger.qualifiers.PerUser
+import com.android.systemui.dreams.DreamMonitor
 import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.keyboard.KeyboardUI
 import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable
 import com.android.systemui.log.SessionTracker
+import com.android.systemui.media.dialog.MediaOutputSwitcherDialogUI
 import com.android.systemui.media.RingtonePlayer
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
 import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
@@ -62,7 +66,10 @@
 /**
  * Collection of {@link CoreStartable}s that should be run on AOSP.
  */
-@Module(includes = [MultiUserUtilsModule::class])
+@Module(includes = [
+    MultiUserUtilsModule::class,
+    StartControlsStartableModule::class
+])
 abstract class SystemUICoreStartableModule {
     /** Inject into AuthController.  */
     @Binds
@@ -217,6 +224,12 @@
     @ClassKey(ToastUI::class)
     abstract fun bindToastUI(service: ToastUI): CoreStartable
 
+    /** Inject into MediaOutputSwitcherDialogUI.  */
+    @Binds
+    @IntoMap
+    @ClassKey(MediaOutputSwitcherDialogUI::class)
+    abstract fun MediaOutputSwitcherDialogUI(sysui: MediaOutputSwitcherDialogUI): CoreStartable
+
     /** Inject into VolumeUI.  */
     @Binds
     @IntoMap
@@ -279,4 +292,18 @@
     @IntoMap
     @ClassKey(StylusUsiPowerStartable::class)
     abstract fun bindStylusUsiPowerStartable(sysui: StylusUsiPowerStartable): CoreStartable
+
+    /** Inject into MuteQuickAffordanceCoreStartable*/
+    @Binds
+    @IntoMap
+    @ClassKey(MuteQuickAffordanceCoreStartable::class)
+    abstract fun bindMuteQuickAffordanceCoreStartable(
+            sysui: MuteQuickAffordanceCoreStartable
+    ): CoreStartable
+
+    /**Inject into DreamMonitor */
+    @Binds
+    @IntoMap
+    @ClassKey(DreamMonitor::class)
+    abstract fun bindDreamMonitor(sysui: DreamMonitor): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 6d13740..6274a26 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -54,6 +54,7 @@
 import com.android.systemui.navigationbar.NavigationBarComponent;
 import com.android.systemui.notetask.NoteTaskModule;
 import com.android.systemui.people.PeopleModule;
+import com.android.systemui.plugins.BcSmartspaceConfigPlugin;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
 import com.android.systemui.privacy.PrivacyModule;
 import com.android.systemui.qs.FgsManagerController;
@@ -62,6 +63,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.security.data.repository.SecurityRepositoryModule;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.smartspace.dagger.SmartspaceModule;
@@ -106,6 +108,8 @@
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
+import javax.inject.Named;
+
 import dagger.Binds;
 import dagger.BindsOptionalOf;
 import dagger.Module;
@@ -195,8 +199,8 @@
 
     @SysUISingleton
     @Provides
-    static SysUiState provideSysUiState(DumpManager dumpManager) {
-        final SysUiState state = new SysUiState();
+    static SysUiState provideSysUiState(DisplayTracker displayTracker, DumpManager dumpManager) {
+        final SysUiState state = new SysUiState(displayTracker);
         dumpManager.registerDumpable(state);
         return state;
     }
@@ -211,6 +215,17 @@
     abstract BcSmartspaceDataPlugin optionalBcSmartspaceDataPlugin();
 
     @BindsOptionalOf
+    abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin();
+
+    @BindsOptionalOf
+    @Named(SmartspaceModule.DATE_SMARTSPACE_DATA_PLUGIN)
+    abstract BcSmartspaceDataPlugin optionalDateSmartspaceConfigPlugin();
+
+    @BindsOptionalOf
+    @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
+    abstract BcSmartspaceDataPlugin optionalWeatherSmartspaceConfigPlugin();
+
+    @BindsOptionalOf
     abstract Recents optionalRecents();
 
     @BindsOptionalOf
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 976afd4..88c0c50 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -45,6 +46,7 @@
     private val statusBarStateController: StatusBarStateController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     @Main private val mainExecutor: Executor,
+    private val logger: ScreenDecorationsLogger,
 ) : DecorProviderFactory() {
     private val display = context.display
     private val displayInfo = DisplayInfo()
@@ -82,7 +84,8 @@
                                         authController,
                                         statusBarStateController,
                                         keyguardUpdateMonitor,
-                                        mainExecutor
+                                        mainExecutor,
+                                        logger,
                                 )
                         )
                     }
@@ -104,7 +107,8 @@
     private val authController: AuthController,
     private val statusBarStateController: StatusBarStateController,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val mainExecutor: Executor
+    private val mainExecutor: Executor,
+    private val logger: ScreenDecorationsLogger,
 ) : BoundDecorProvider() {
     override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
 
@@ -136,7 +140,8 @@
                 alignedBound,
                 statusBarStateController,
                 keyguardUpdateMonitor,
-                mainExecutor
+                mainExecutor,
+                logger,
         )
         view.id = viewId
         view.setColor(tintColor)
@@ -155,8 +160,9 @@
         layoutParams.let { lp ->
             lp.width = ViewGroup.LayoutParams.MATCH_PARENT
             lp.height = ViewGroup.LayoutParams.MATCH_PARENT
+            logger.faceSensorLocation(authController.faceSensorLocation)
             authController.faceSensorLocation?.y?.let { faceAuthSensorHeight ->
-                val faceScanningHeight = (faceAuthSensorHeight * 2).toInt()
+                val faceScanningHeight = (faceAuthSensorHeight * 2)
                 when (rotation) {
                     Surface.ROTATION_0, Surface.ROTATION_180 ->
                         lp.height = faceScanningHeight
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index 2a3d67f..c331164 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -17,12 +17,12 @@
 package com.android.systemui.doze;
 
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.doze.DozeMachine.State;
 import com.android.systemui.doze.dagger.DozeScope;
+import com.android.systemui.settings.UserTracker;
 
 import java.io.PrintWriter;
 
@@ -40,14 +40,17 @@
     private final AmbientDisplayConfiguration mConfig;
     private DozeMachine mMachine;
     private final DockManager mDockManager;
+    private final UserTracker mUserTracker;
     private final DockEventListener mDockEventListener;
 
     private int mDockState = DockManager.STATE_NONE;
 
     @Inject
-    DozeDockHandler(AmbientDisplayConfiguration config, DockManager dockManager) {
+    DozeDockHandler(AmbientDisplayConfiguration config, DockManager dockManager,
+            UserTracker userTracker) {
         mConfig = config;
         mDockManager = dockManager;
+        mUserTracker = userTracker;
         mDockEventListener = new DockEventListener();
     }
 
@@ -100,7 +103,7 @@
                     nextState = State.DOZE_AOD_DOCKED;
                     break;
                 case DockManager.STATE_NONE:
-                    nextState = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) ? State.DOZE_AOD
+                    nextState = mConfig.alwaysOnEnabled(mUserTracker.getUserId()) ? State.DOZE_AOD
                             : State.DOZE;
                     break;
                 case DockManager.STATE_DOCKED_HIDE:
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 96c35d4..fc3263f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -23,7 +23,6 @@
 import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Trace;
-import android.os.UserHandle;
 import android.util.Log;
 import android.view.Display;
 
@@ -33,6 +32,7 @@
 import com.android.systemui.doze.dagger.WrappedService;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -149,6 +149,7 @@
     private final DozeHost mDozeHost;
     private final DockManager mDockManager;
     private final Part[] mParts;
+    private final UserTracker mUserTracker;
 
     private final ArrayList<State> mQueuedRequests = new ArrayList<>();
     private State mState = State.UNINITIALIZED;
@@ -161,7 +162,7 @@
             AmbientDisplayConfiguration ambientDisplayConfig,
             WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle,
             DozeLog dozeLog, DockManager dockManager,
-            DozeHost dozeHost, Part[] parts) {
+            DozeHost dozeHost, Part[] parts, UserTracker userTracker) {
         mDozeService = service;
         mAmbientDisplayConfig = ambientDisplayConfig;
         mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -170,6 +171,7 @@
         mDockManager = dockManager;
         mDozeHost = dozeHost;
         mParts = parts;
+        mUserTracker = userTracker;
         for (Part part : parts) {
             part.setDozeMachine(this);
         }
@@ -429,7 +431,7 @@
                     nextState = State.FINISH;
                 } else if (mDockManager.isDocked()) {
                     nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED;
-                } else if (mAmbientDisplayConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+                } else if (mAmbientDisplayConfig.alwaysOnEnabled(mUserTracker.getUserId())) {
                     nextState = State.DOZE_AOD;
                 } else {
                     nextState = State.DOZE;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 937884c..4cade77 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -43,6 +43,7 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.settings.SystemSettings;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -78,6 +79,7 @@
     private final DozeParameters mDozeParameters;
     private final DevicePostureController mDevicePostureController;
     private final DozeLog mDozeLog;
+    private final SystemSettings mSystemSettings;
     private final int[] mSensorToBrightness;
     private final int[] mSensorToScrimOpacity;
     private final int mScreenBrightnessDim;
@@ -110,7 +112,8 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             DozeParameters dozeParameters,
             DevicePostureController devicePostureController,
-            DozeLog dozeLog) {
+            DozeLog dozeLog,
+            SystemSettings systemSettings) {
         mContext = context;
         mDozeService = service;
         mSensorManager = sensorManager;
@@ -122,6 +125,7 @@
         mDozeHost = host;
         mHandler = handler;
         mDozeLog = dozeLog;
+        mSystemSettings = systemSettings;
 
         mScreenBrightnessMinimumDimAmountFloat = context.getResources().getFloat(
                 R.dimen.config_screenBrightnessMinimumDimAmountFloat);
@@ -257,7 +261,7 @@
     }
     //TODO: brightnessfloat change usages to float.
     private int clampToUserSetting(int brightness) {
-        int userSetting = Settings.System.getIntForUser(mContext.getContentResolver(),
+        int userSetting = mSystemSettings.getIntForUser(
                 Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE,
                 UserHandle.USER_CURRENT);
         return Math.min(brightness, userSetting);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 37183e8..e3c568a9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -116,6 +116,7 @@
     private boolean mListening;
     private boolean mListeningTouchScreenSensors;
     private boolean mListeningProxSensors;
+    private boolean mListeningAodOnlySensors;
     private boolean mUdfpsEnrolled;
 
     @DevicePostureController.DevicePostureInt
@@ -184,7 +185,8 @@
                         dozeParameters.getPulseOnSigMotion(),
                         DozeLog.PULSE_REASON_SENSOR_SIGMOTION,
                         false /* touchCoords */,
-                        false /* touchscreen */),
+                        false /* touchscreen */
+                ),
                 new TriggerSensor(
                         mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
                         Settings.Secure.DOZE_PICK_UP_GESTURE,
@@ -195,14 +197,17 @@
                         false /* touchscreen */,
                         false /* ignoresSetting */,
                         false /* requires prox */,
-                        true /* immediatelyReRegister */),
+                        true /* immediatelyReRegister */,
+                        false /* requiresAod */
+                ),
                 new TriggerSensor(
                         findSensor(config.doubleTapSensorType()),
                         Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
                         true /* configured */,
                         DozeLog.REASON_SENSOR_DOUBLE_TAP,
                         dozeParameters.doubleTapReportsTouchCoordinates(),
-                        true /* touchscreen */),
+                        true /* touchscreen */
+                ),
                 new TriggerSensor(
                         findSensors(config.tapSensorTypeMapping()),
                         Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
@@ -214,7 +219,9 @@
                         false /* ignoresSetting */,
                         dozeParameters.singleTapUsesProx(mDevicePosture) /* requiresProx */,
                         true /* immediatelyReRegister */,
-                        mDevicePosture),
+                        mDevicePosture,
+                        false
+                ),
                 new TriggerSensor(
                         findSensor(config.longPressSensorType()),
                         Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
@@ -225,7 +232,9 @@
                         true /* touchscreen */,
                         false /* ignoresSetting */,
                         dozeParameters.longPressUsesProx() /* requiresProx */,
-                        true /* immediatelyReRegister */),
+                        true /* immediatelyReRegister */,
+                        false /* requiresAod */
+                ),
                 new TriggerSensor(
                         findSensor(config.udfpsLongPressSensorType()),
                         "doze_pulse_on_auth",
@@ -236,15 +245,18 @@
                         true /* touchscreen */,
                         false /* ignoresSetting */,
                         dozeParameters.longPressUsesProx(),
-                        false /* immediatelyReRegister */),
+                        false /* immediatelyReRegister */,
+                        true /* requiresAod */
+                ),
                 new PluginSensor(
                         new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
                         Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
                         mConfig.wakeScreenGestureAvailable()
-                          && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT),
+                          && mConfig.alwaysOnEnabled(mUserTracker.getUserId()),
                         DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
                         false /* reports touch coordinates */,
-                        false /* touchscreen */),
+                        false /* touchscreen */
+                ),
                 new PluginSensor(
                         new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN),
                         Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
@@ -252,7 +264,8 @@
                         DozeLog.PULSE_REASON_SENSOR_WAKE_REACH,
                         false /* reports touch coordinates */,
                         false /* touchscreen */,
-                        mConfig.getWakeLockScreenDebounce()),
+                        mConfig.getWakeLockScreenDebounce()
+                ),
                 new TriggerSensor(
                         findSensor(config.quickPickupSensorType()),
                         Settings.Secure.DOZE_QUICK_PICKUP_GESTURE,
@@ -263,7 +276,9 @@
                         false /* requiresTouchscreen */,
                         false /* ignoresSetting */,
                         false /* requiresProx */,
-                        true /* immediatelyReRegister */),
+                        true /* immediatelyReRegister */,
+                        false /* requiresAod */
+                ),
         };
         setProxListening(false);  // Don't immediately start listening when we register.
         mProximitySensor.register(
@@ -278,7 +293,7 @@
 
     private boolean udfpsLongPressConfigured() {
         return mUdfpsEnrolled
-                && (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) || mScreenOffUdfpsEnabled);
+                && (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) || mScreenOffUdfpsEnabled);
     }
 
     private boolean quickPickUpConfigured() {
@@ -357,29 +372,36 @@
     /**
      * If sensors should be registered and sending signals.
      */
-    public void setListening(boolean listen, boolean includeTouchScreenSensors) {
-        if (mListening == listen && mListeningTouchScreenSensors == includeTouchScreenSensors) {
+    public void setListening(boolean listen, boolean includeTouchScreenSensors,
+            boolean includeAodOnlySensors) {
+        if (mListening == listen && mListeningTouchScreenSensors == includeTouchScreenSensors
+                && mListeningAodOnlySensors == includeAodOnlySensors) {
             return;
         }
         mListening = listen;
         mListeningTouchScreenSensors = includeTouchScreenSensors;
+        mListeningAodOnlySensors = includeAodOnlySensors;
         updateListening();
     }
 
     /**
      * If sensors should be registered and sending signals.
      */
-    public void setListening(boolean listen, boolean includeTouchScreenSensors,
-            boolean lowPowerStateOrOff) {
+    public void setListeningWithPowerState(boolean listen, boolean includeTouchScreenSensors,
+            boolean includeAodRequiringSensors, boolean lowPowerStateOrOff) {
         final boolean shouldRegisterProxSensors =
                 !mSelectivelyRegisterProxSensors || lowPowerStateOrOff;
-        if (mListening == listen && mListeningTouchScreenSensors == includeTouchScreenSensors
-                && mListeningProxSensors == shouldRegisterProxSensors) {
+        if (mListening == listen
+                && mListeningTouchScreenSensors == includeTouchScreenSensors
+                && mListeningProxSensors == shouldRegisterProxSensors
+                && mListeningAodOnlySensors == includeAodRequiringSensors
+        ) {
             return;
         }
         mListening = listen;
         mListeningTouchScreenSensors = includeTouchScreenSensors;
         mListeningProxSensors = shouldRegisterProxSensors;
+        mListeningAodOnlySensors = includeAodRequiringSensors;
         updateListening();
     }
 
@@ -391,7 +413,8 @@
         for (TriggerSensor s : mTriggerSensors) {
             boolean listen = mListening
                     && (!s.mRequiresTouchscreen || mListeningTouchScreenSensors)
-                    && (!s.mRequiresProx || mListeningProxSensors);
+                    && (!s.mRequiresProx || mListeningProxSensors)
+                    && (!s.mRequiresAod || mListeningAodOnlySensors);
             s.setListening(listen);
             if (listen) {
                 anyListening = true;
@@ -499,6 +522,9 @@
         private final boolean mRequiresTouchscreen;
         private final boolean mRequiresProx;
 
+        // Whether the sensor should only register if the device is in AOD
+        private final boolean mRequiresAod;
+
         // Whether to immediately re-register this sensor after the sensor is triggered.
         // If false, the sensor registration will be updated on the next AOD state transition.
         private final boolean mImmediatelyReRegister;
@@ -527,7 +553,8 @@
                     requiresTouchscreen,
                     false /* ignoresSetting */,
                     false /* requiresProx */,
-                    true /* immediatelyReRegister */
+                    true /* immediatelyReRegister */,
+                    false
             );
         }
 
@@ -541,7 +568,8 @@
                 boolean requiresTouchscreen,
                 boolean ignoresSetting,
                 boolean requiresProx,
-                boolean immediatelyReRegister
+                boolean immediatelyReRegister,
+                boolean requiresAod
         ) {
             this(
                     new Sensor[]{ sensor },
@@ -554,7 +582,8 @@
                     ignoresSetting,
                     requiresProx,
                     immediatelyReRegister,
-                    DevicePostureController.DEVICE_POSTURE_UNKNOWN
+                    DevicePostureController.DEVICE_POSTURE_UNKNOWN,
+                    requiresAod
             );
         }
 
@@ -569,7 +598,8 @@
                 boolean ignoresSetting,
                 boolean requiresProx,
                 boolean immediatelyReRegister,
-                @DevicePostureController.DevicePostureInt int posture
+                @DevicePostureController.DevicePostureInt int posture,
+                boolean requiresAod
         ) {
             mSensors = sensors;
             mSetting = setting;
@@ -580,6 +610,7 @@
             mRequiresTouchscreen = requiresTouchscreen;
             mIgnoresSetting = ignoresSetting;
             mRequiresProx = requiresProx;
+            mRequiresAod = requiresAod;
             mPosture = posture;
             mImmediatelyReRegister = immediatelyReRegister;
         }
@@ -663,13 +694,13 @@
         }
 
         protected boolean enabledBySetting() {
-            if (!mConfig.enabled(UserHandle.USER_CURRENT)) {
+            if (!mConfig.enabled(mUserTracker.getUserId())) {
                 return false;
             } else if (TextUtils.isEmpty(mSetting)) {
                 return true;
             }
             return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0,
-                    UserHandle.USER_CURRENT) != 0;
+                    mUserTracker.getUserId()) != 0;
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
index e6d9865..de0bdd3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
@@ -20,10 +20,10 @@
 
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.PowerManager;
-import android.os.UserHandle;
 import android.text.TextUtils;
 
 import com.android.systemui.doze.dagger.DozeScope;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 
 import java.io.PrintWriter;
@@ -57,6 +57,7 @@
     private final AmbientDisplayConfiguration mConfig;
     private final DozeLog mDozeLog;
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
+    private final UserTracker mUserTracker;
 
     private boolean mIsCarModeEnabled = false;
 
@@ -65,11 +66,13 @@
             DozeHost dozeHost,
             AmbientDisplayConfiguration config,
             DozeLog dozeLog,
-            Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
+            Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
+            UserTracker userTracker) {
         mDozeHost = dozeHost;
         mConfig = config;
         mDozeLog = dozeLog;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -148,7 +151,7 @@
 
     private void handleCarModeExited() {
         mDozeLog.traceCarModeEnded();
-        mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
+        mMachine.requestState(mConfig.alwaysOnEnabled(mUserTracker.getUserId())
                 ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
     }
 
@@ -166,7 +169,7 @@
             if (mDozeHost.isPowerSaveActive()) {
                 nextState = DozeMachine.State.DOZE;
             } else if (mMachine.getState() == DozeMachine.State.DOZE
-                    && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
+                    && mConfig.alwaysOnEnabled(mUserTracker.getUserId())) {
                 nextState = DozeMachine.State.DOZE_AOD;
             }
 
@@ -181,7 +184,7 @@
             // handles suppression changes, while DozeMachine#transitionPolicy handles gating
             // transitions to DOZE_AOD
             final DozeMachine.State nextState;
-            if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) && !suppressed) {
+            if (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) && !suppressed) {
                 nextState = DozeMachine.State.DOZE_AOD;
             } else {
                 nextState = DozeMachine.State.DOZE;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index b95c3f3..b709608 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -111,6 +111,7 @@
     private boolean mWantProxSensor;
     private boolean mWantTouchScreenSensors;
     private boolean mWantSensors;
+    private boolean mInAod;
 
     private final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
@@ -460,12 +461,19 @@
                 mDozeSensors.requestTemporaryDisable();
                 break;
             case DOZE:
-            case DOZE_AOD:
                 mAodInterruptRunnable = null;
-                mWantProxSensor = newState != DozeMachine.State.DOZE;
+                mWantProxSensor = false;
                 mWantSensors = true;
                 mWantTouchScreenSensors = true;
-                if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) {
+                mInAod = false;
+                break;
+            case DOZE_AOD:
+                mAodInterruptRunnable = null;
+                mWantProxSensor = true;
+                mWantSensors = true;
+                mWantTouchScreenSensors = true;
+                mInAod = true;
+                if (!sWakeDisplaySensorState) {
                     onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE);
                 }
                 break;
@@ -491,7 +499,7 @@
                 break;
             default:
         }
-        mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors);
+        mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors, mInAod);
     }
 
     private void registerCallbacks() {
@@ -510,11 +518,12 @@
 
     private void stopListeningToAllTriggers() {
         unregisterCallbacks();
-        mDozeSensors.setListening(false, false);
+        mDozeSensors.setListening(false, false, false);
         mDozeSensors.setProxListening(false);
         mWantSensors = false;
         mWantProxSensor = false;
         mWantTouchScreenSensors = false;
+        mInAod = false;
     }
 
     @Override
@@ -523,7 +532,8 @@
         final boolean lowPowerStateOrOff = state == Display.STATE_DOZE
                 || state == Display.STATE_DOZE_SUSPEND || state == Display.STATE_OFF;
         mDozeSensors.setProxListening(mWantProxSensor && lowPowerStateOrOff);
-        mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors, lowPowerStateOrOff);
+        mDozeSensors.setListeningWithPowerState(mWantSensors, mWantTouchScreenSensors,
+                mInAod, lowPowerStateOrOff);
 
         if (mAodInterruptRunnable != null && state == Display.STATE_ON) {
             mAodInterruptRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
new file mode 100644
index 0000000..102f208
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
@@ -0,0 +1,58 @@
+/*
+ * 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.systemui.dreams;
+
+import android.util.Log;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback;
+import com.android.systemui.dreams.conditions.DreamCondition;
+import com.android.systemui.shared.condition.Monitor;
+
+import javax.inject.Inject;
+
+/**
+ * A {@link CoreStartable} to retain a monitor for tracking dreaming.
+ */
+public class DreamMonitor implements CoreStartable {
+    private static final String TAG = "DreamMonitor";
+
+    // We retain a reference to the monitor so it is not garbage-collected.
+    private final Monitor mConditionMonitor;
+    private final DreamCondition mDreamCondition;
+    private final DreamStatusBarStateCallback mCallback;
+
+
+    @Inject
+    public DreamMonitor(Monitor monitor, DreamCondition dreamCondition,
+            DreamStatusBarStateCallback callback) {
+        mConditionMonitor = monitor;
+        mDreamCondition = dreamCondition;
+        mCallback = callback;
+
+    }
+    @Override
+    public void start() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "started");
+        }
+
+        mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
+                .addCondition(mDreamCondition)
+                .build());
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index c882f8a..c3bd5d9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -182,6 +182,18 @@
             }
     }
 
+    /**
+     * Ends the dream content and dream overlay animations, if they're currently running.
+     * @see [AnimatorSet.end]
+     */
+    fun endAnimations() {
+        mAnimator =
+            mAnimator?.let {
+                it.end()
+                null
+            }
+    }
+
     private fun blurAnimator(
         view: View,
         fromBlurRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 3106173..50cfb6a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -37,10 +37,11 @@
 import com.android.systemui.dreams.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayModule;
+import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.BlurUtils;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -55,7 +56,6 @@
 @DreamOverlayComponent.DreamOverlayScope
 public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> {
     private final DreamOverlayStatusBarViewController mStatusBarViewController;
-    private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final BlurUtils mBlurUtils;
     private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
     private final DreamOverlayStateController mStateController;
@@ -84,9 +84,26 @@
     private long mJitterStartTimeMillis;
 
     private boolean mBouncerAnimating;
+    private boolean mWakingUpFromSwipe;
 
-    private final KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback =
-            new KeyguardBouncer.PrimaryBouncerExpansionCallback() {
+    private final BouncerlessScrimController mBouncerlessScrimController;
+
+    private final BouncerlessScrimController.Callback mBouncerlessExpansionCallback =
+            new BouncerlessScrimController.Callback() {
+        @Override
+        public void onExpansion(ShadeExpansionChangeEvent event) {
+            updateTransitionState(event.getFraction());
+        }
+
+        @Override
+        public void onWakeup() {
+            mWakingUpFromSwipe = true;
+        }
+    };
+
+    private final PrimaryBouncerExpansionCallback
+            mBouncerExpansionCallback =
+            new PrimaryBouncerExpansionCallback() {
 
                 @Override
                 public void onStartingToShow() {
@@ -125,13 +142,29 @@
                 }
             };
 
+    /**
+     * If true, overlay entry animations should be skipped once.
+     *
+     * This is turned on when exiting low light and should be turned off once the entry animations
+     * are skipped once.
+     */
+    private boolean mSkipEntryAnimations;
+
+    private final DreamOverlayStateController.Callback
+            mDreamOverlayStateCallback =
+            new DreamOverlayStateController.Callback() {
+                @Override
+                public void onExitLowLight() {
+                    mSkipEntryAnimations = true;
+                }
+            };
+
     @Inject
     public DreamOverlayContainerViewController(
             DreamOverlayContainerView containerView,
             ComplicationHostViewController complicationHostViewController,
             @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
             DreamOverlayStatusBarViewController statusBarViewController,
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             BlurUtils blurUtils,
             @Main Handler handler,
             @Main Resources resources,
@@ -141,15 +174,18 @@
             @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
             PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor,
             DreamOverlayAnimationsController animationsController,
-            DreamOverlayStateController stateController) {
+            DreamOverlayStateController stateController,
+            BouncerlessScrimController bouncerlessScrimController) {
         super(containerView);
         mDreamOverlayContentView = contentView;
         mStatusBarViewController = statusBarViewController;
-        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mBlurUtils = blurUtils;
         mDreamOverlayAnimationsController = animationsController;
         mStateController = stateController;
 
+        mBouncerlessScrimController = bouncerlessScrimController;
+        mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
+
         mComplicationHostViewController = complicationHostViewController;
         mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
                 R.dimen.dream_overlay_y_offset);
@@ -168,6 +204,7 @@
 
     @Override
     protected void onInit() {
+        mStateController.addCallback(mDreamOverlayStateCallback);
         mStatusBarViewController.init();
         mComplicationHostViewController.init();
         mDreamOverlayAnimationsController.init(mView);
@@ -175,27 +212,27 @@
 
     @Override
     protected void onViewAttached() {
+        mWakingUpFromSwipe = false;
         mJitterStartTimeMillis = System.currentTimeMillis();
         mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval);
-        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
-        if (bouncer != null) {
-            bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
-        }
         mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
 
         // Start dream entry animations. Skip animations for low light clock.
         if (!mStateController.isLowLightActive()) {
             mDreamOverlayAnimationsController.startEntryAnimations();
+
+            if (mSkipEntryAnimations) {
+                // If we're transitioning from the low light dream back to the user dream, skip the
+                // overlay animations and show immediately.
+                mDreamOverlayAnimationsController.endAnimations();
+                mSkipEntryAnimations = false;
+            }
         }
     }
 
     @Override
     protected void onViewDetached() {
         mHandler.removeCallbacks(this::updateBurnInOffsets);
-        final KeyguardBouncer bouncer = mStatusBarKeyguardViewManager.getPrimaryBouncer();
-        if (bouncer != null) {
-            bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
-        }
         mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
 
         mDreamOverlayAnimationsController.cancelAnimations();
@@ -264,6 +301,13 @@
      */
     public void wakeUp(@NonNull Runnable onAnimationEnd,
             @NonNull DelayableExecutor callbackExecutor) {
+        // When swiping causes wakeup, do not run any animations as the dream should exit as soon
+        // as possible.
+        if (mWakingUpFromSwipe) {
+            onAnimationEnd.run();
+            return;
+        }
+
         mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index a9a9cae..dd01be0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -208,6 +208,13 @@
         });
     }
 
+    @Override
+    public void onEndDream() {
+        mExecutor.execute(() -> {
+            resetCurrentDreamOverlayLocked();
+        });
+    }
+
     private Lifecycle.State getCurrentStateLocked() {
         return mLifecycleRegistry.getCurrentState();
     }
@@ -291,6 +298,7 @@
         mDreamOverlayContainerViewController = null;
         mDreamOverlayTouchMonitor = null;
 
+        mWindow = null;
         mStarted = false;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index ccfdd096..2c7ecb1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -83,6 +83,12 @@
          */
         default void onAvailableComplicationTypesChanged() {
         }
+
+        /**
+         * Called when the low light dream is exiting and transitioning back to the user dream.
+         */
+        default void onExitLowLight() {
+        }
     }
 
     private final Executor mExecutor;
@@ -278,6 +284,10 @@
      * @param active {@code true} if low light mode is active, {@code false} otherwise.
      */
     public void setLowLightActive(boolean active) {
+        if (isLowLightActive() && !active) {
+            // Notify that we're exiting low light only on the transition from active to not active.
+            mCallbacks.forEach(Callback::onExitLowLight);
+        }
         modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_LOW_LIGHT_ACTIVE);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index f1bb156..90c440c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -25,7 +25,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.format.DateFormat;
 import android.util.PluralsMessageFormatter;
@@ -37,6 +36,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -72,6 +72,7 @@
             mDreamOverlayNotificationCountProvider;
     private final ZenModeController mZenModeController;
     private final DreamOverlayStateController mDreamOverlayStateController;
+    private final UserTracker mUserTracker;
     private final StatusBarWindowStateController mStatusBarWindowStateController;
     private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;
     private final Executor mMainExecutor;
@@ -154,7 +155,8 @@
             ZenModeController zenModeController,
             StatusBarWindowStateController statusBarWindowStateController,
             DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
-            DreamOverlayStateController dreamOverlayStateController) {
+            DreamOverlayStateController dreamOverlayStateController,
+            UserTracker userTracker) {
         super(view);
         mResources = resources;
         mMainExecutor = mainExecutor;
@@ -169,6 +171,7 @@
         mStatusBarItemsProvider = statusBarItemsProvider;
         mZenModeController = zenModeController;
         mDreamOverlayStateController = dreamOverlayStateController;
+        mUserTracker = userTracker;
 
         // Register to receive show/hide updates for the system status bar. Our custom status bar
         // needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -259,7 +262,7 @@
 
     private void updateAlarmStatusIcon() {
         final AlarmManager.AlarmClockInfo alarm =
-                mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+                mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
         final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
         showIcon(
                 DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/callbacks/DreamStatusBarStateCallback.java b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/DreamStatusBarStateCallback.java
new file mode 100644
index 0000000..c8c9470
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/callbacks/DreamStatusBarStateCallback.java
@@ -0,0 +1,46 @@
+/*
+ * 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.systemui.dreams.callbacks;
+
+import android.util.Log;
+
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+
+import javax.inject.Inject;
+
+/**
+ * A callback that informs {@link SysuiStatusBarStateController} when the dream state has changed.
+ */
+public class DreamStatusBarStateCallback implements Monitor.Callback {
+    private static final String TAG = "DreamStatusBarCallback";
+
+    private final SysuiStatusBarStateController mStateController;
+
+    @Inject
+    public DreamStatusBarStateCallback(SysuiStatusBarStateController statusBarStateController) {
+        mStateController = statusBarStateController;
+    }
+
+    @Override
+    public void onConditionsChanged(boolean allConditionsMet) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onConditionChanged:" + allConditionsMet);
+        }
+
+        mStateController.setIsDreaming(allConditionsMet);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
new file mode 100644
index 0000000..2befce7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java
@@ -0,0 +1,73 @@
+/*
+ * 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.systemui.dreams.conditions;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.text.TextUtils;
+
+import com.android.systemui.shared.condition.Condition;
+
+import javax.inject.Inject;
+
+/**
+ * {@link DreamCondition} provides a signal when a dream begins and ends.
+ */
+public class DreamCondition extends Condition {
+    private final Context mContext;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            processIntent(intent);
+        }
+    };
+
+    @Inject
+    public DreamCondition(Context context) {
+        mContext = context;
+    }
+
+    private void processIntent(Intent intent) {
+        // In the case of a non-existent sticky broadcast, ignore when there is no intent.
+        if (intent == null) {
+            return;
+        }
+        if (TextUtils.equals(intent.getAction(), Intent.ACTION_DREAMING_STARTED)) {
+            updateCondition(true);
+        } else if (TextUtils.equals(intent.getAction(), Intent.ACTION_DREAMING_STOPPED)) {
+            updateCondition(false);
+        } else {
+            throw new IllegalStateException("unexpected intent:" + intent);
+        }
+    }
+
+    @Override
+    protected void start() {
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_DREAMING_STARTED);
+        filter.addAction(Intent.ACTION_DREAMING_STOPPED);
+        final Intent stickyIntent = mContext.registerReceiver(mReceiver, filter);
+        processIntent(stickyIntent);
+    }
+
+    @Override
+    protected void stop() {
+        mContext.unregisterReceiver(mReceiver);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index e7b29bb..0ab8c8e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -28,6 +28,7 @@
 import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
 import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
+import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
 
 import java.util.Optional;
 
@@ -42,6 +43,7 @@
 @Module(includes = {
             RegisteredComplicationsModule.class,
             LowLightDreamModule.class,
+            ScrimModule.class
         },
         subcomponents = {
             DreamOverlayComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 44207f4..73c2289 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -36,11 +36,12 @@
 
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.dreams.touch.scrim.ScrimController;
+import com.android.systemui.dreams.touch.scrim.ScrimManager;
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import java.util.Optional;
@@ -78,7 +79,8 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final float mBouncerZoneScreenPercentage;
 
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final ScrimManager mScrimManager;
+    private ScrimController mCurrentScrimController;
     private float mCurrentExpansion;
     private final Optional<CentralSurfaces> mCentralSurfaces;
 
@@ -90,6 +92,7 @@
     private final DisplayMetrics mDisplayMetrics;
 
     private Boolean mCapture;
+    private Boolean mExpanded;
 
     private boolean mBouncerInitiallyShowing;
 
@@ -101,6 +104,17 @@
 
     private final UiEventLogger mUiEventLogger;
 
+    private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() {
+        @Override
+        public void onScrimControllerChanged(ScrimController controller) {
+            if (mCurrentScrimController != null) {
+                mCurrentScrimController.reset();
+            }
+
+            mCurrentScrimController = controller;
+        }
+    };
+
     private final GestureDetector.OnGestureListener mOnGestureListener =
             new GestureDetector.SimpleOnGestureListener() {
                 @Override
@@ -115,8 +129,10 @@
                                 .orElse(false);
 
                         if (mCapture) {
+                            // reset expanding
+                            mExpanded = false;
                             // Since the user is dragging the bouncer up, set scrimmed to false.
-                            mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
+                            mCurrentScrimController.show();
                         }
                     }
 
@@ -157,10 +173,10 @@
         ShadeExpansionChangeEvent event =
                 new ShadeExpansionChangeEvent(
                         /* fraction= */ mCurrentExpansion,
-                        /* expanded= */ false,
+                        /* expanded= */ mExpanded,
                         /* tracking= */ true,
                         /* dragDownPxAmount= */ dragDownAmount);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(event);
+        mCurrentScrimController.expand(event);
     }
 
 
@@ -187,7 +203,7 @@
     @Inject
     public BouncerSwipeTouchHandler(
             DisplayMetrics displayMetrics,
-            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            ScrimManager scrimManager,
             Optional<CentralSurfaces> centralSurfaces,
             NotificationShadeWindowController notificationShadeWindowController,
             ValueAnimatorCreator valueAnimatorCreator,
@@ -200,7 +216,7 @@
             UiEventLogger uiEventLogger) {
         mDisplayMetrics = displayMetrics;
         mCentralSurfaces = centralSurfaces;
-        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mScrimManager = scrimManager;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mBouncerZoneScreenPercentage = swipeRegionPercentage;
         mFlingAnimationUtils = flingAnimationUtils;
@@ -234,9 +250,12 @@
         mTouchSession = session;
         mVelocityTracker.clear();
         mNotificationShadeWindowController.setForcePluginOpen(true, this);
+        mScrimManager.addCallback(mScrimManagerCallback);
+        mCurrentScrimController = mScrimManager.getCurrentController();
 
         session.registerCallback(() -> {
             mVelocityTracker.recycle();
+            mScrimManager.removeCallback(mScrimManagerCallback);
             mCapture = null;
             mNotificationShadeWindowController.setForcePluginOpen(false, this);
         });
@@ -273,9 +292,10 @@
                 final float velocityVector =
                         (float) Math.hypot(horizontalVelocity, verticalVelocity);
 
-                final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector)
-                        ? KeyguardBouncerConstants.EXPANSION_HIDDEN
-                        : KeyguardBouncerConstants.EXPANSION_VISIBLE;
+                mExpanded = !flingRevealsOverlay(verticalVelocity, velocityVector);
+                final float expansion = mExpanded
+                        ? KeyguardBouncerConstants.EXPANSION_VISIBLE
+                        : KeyguardBouncerConstants.EXPANSION_HIDDEN;
 
                 // Log the swiping up to show Bouncer event.
                 if (!mBouncerInitiallyShowing
@@ -286,7 +306,7 @@
                 flingToExpansion(verticalVelocity, expansion);
 
                 if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
-                    mStatusBarKeyguardViewManager.reset(false);
+                    mCurrentScrimController.reset();
                 }
                 break;
             default:
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 695b59a..b8b459e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -226,6 +226,15 @@
             return;
         }
 
+        // When we stop monitoring touches, we must ensure that all active touch sessions and
+        // descendants informed of the removal so any cleanup for active tracking can proceed.
+        mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
+            while (touchSession != null) {
+                touchSession.onRemoved();
+                touchSession = touchSession.getPredecessor();
+            }
+        }));
+
         mCurrentInputSession.dispose();
         mCurrentInputSession = null;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
index 4382757..e1d0339 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
@@ -21,10 +21,10 @@
 
 import android.os.Looper;
 import android.view.Choreographer;
-import android.view.Display;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -55,8 +55,9 @@
     public InputSession(@Named(INPUT_SESSION_NAME) String sessionName,
             InputChannelCompat.InputEventListener inputEventListener,
             GestureDetector.OnGestureListener gestureListener,
+            DisplayTracker displayTracker,
             @Named(PILFER_ON_GESTURE_CONSUME) boolean pilferOnGestureConsume) {
-        mInputMonitor = new InputMonitorCompat(sessionName, Display.DEFAULT_DISPLAY);
+        mInputMonitor = new InputMonitorCompat(sessionName, displayTracker.getDefaultDisplayId());
         mGestureDetector = new GestureDetector(gestureListener);
 
         mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
new file mode 100644
index 0000000..f5bbba7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
@@ -0,0 +1,49 @@
+/*
+ * 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.systemui.dreams.touch.scrim;
+
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+
+import javax.inject.Inject;
+
+/**
+ * Implementation for handling swipe movements on the overlay when the keyguard is present.
+ */
+public class BouncerScrimController implements ScrimController {
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+
+    @Inject
+    BouncerScrimController(StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+    }
+
+    @Override
+    public void show() {
+        mStatusBarKeyguardViewManager.showBouncer(false);
+    }
+
+    @Override
+    public void expand(ShadeExpansionChangeEvent event) {
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(event);
+    }
+
+    @Override
+    public void reset() {
+        mStatusBarKeyguardViewManager.reset(false);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java
new file mode 100644
index 0000000..01e4d04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java
@@ -0,0 +1,91 @@
+/*
+ * 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.systemui.dreams.touch.scrim;
+
+import android.os.PowerManager;
+import android.os.SystemClock;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.unfold.util.CallbackController;
+
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * {@link BouncerlessScrimController} handles scrim progression when no keyguard is set. When
+ * fully expanded, the controller dismisses the dream.
+ */
+@SysUISingleton
+public class BouncerlessScrimController implements ScrimController,
+        CallbackController<BouncerlessScrimController.Callback> {
+    private static final String TAG = "BLScrimController";
+
+    /**
+     * {@link Callback} allows {@link BouncerlessScrimController} clients to be informed of
+     * expansion progression and wakeup
+     */
+    public interface Callback {
+        /**
+         * Invoked when there is a change to the scrim expansion.
+         */
+        void onExpansion(ShadeExpansionChangeEvent event);
+
+        /**
+         * Invoked after {@link BouncerlessScrimController} has started waking up the device.
+         */
+        void onWakeup();
+    }
+
+    private final Executor mExecutor;
+    private final PowerManager mPowerManager;
+
+    @Override
+    public void addCallback(Callback listener) {
+        mExecutor.execute(() -> mCallbacks.add(listener));
+    }
+
+    @Override
+    public void removeCallback(Callback listener) {
+        mExecutor.execute(() -> mCallbacks.remove(listener));
+    }
+
+    private final HashSet<Callback> mCallbacks;
+
+
+    @Inject
+    public BouncerlessScrimController(@Main Executor executor,
+            PowerManager powerManager) {
+        mExecutor = executor;
+        mPowerManager = powerManager;
+        mCallbacks = new HashSet<>();
+    }
+
+    @Override
+    public void expand(ShadeExpansionChangeEvent event) {
+        if (event.getExpanded())  {
+            mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+                    "com.android.systemui:SwipeUp");
+            mExecutor.execute(() -> mCallbacks.forEach(callback -> callback.onWakeup()));
+        } else {
+            mExecutor.execute(() -> mCallbacks.forEach(callback -> callback.onExpansion(event)));
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java
new file mode 100644
index 0000000..61629ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java
@@ -0,0 +1,44 @@
+/*
+ * 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.systemui.dreams.touch.scrim;
+
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+
+/**
+ * {@link ScrimController} provides an interface for the different consumers of scrolling/expansion
+ * events over the dream.
+ */
+public interface ScrimController {
+    /**
+     * Called at the start of expansion before any expansion amount updates.
+     */
+    default void show() {
+    }
+
+    /**
+     * Called for every expansion update.
+     * @param event {@link ShadeExpansionChangeEvent} detailing the change.
+     */
+    default void expand(ShadeExpansionChangeEvent event) {
+    }
+
+    /**
+     * Called at the end of the movement.
+     */
+    default void reset() {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java
new file mode 100644
index 0000000..0d0dff6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java
@@ -0,0 +1,112 @@
+/*
+ * 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.systemui.dreams.touch.scrim;
+
+import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCERLESS_SCRIM_CONTROLLER;
+import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCER_SCRIM_CONTROLLER;
+
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * {@link ScrimManager} helps manage multiple {@link ScrimController} instances, specifying the
+ * appropriate one to use at the current moment and managing the handoff between controllers.
+ */
+public class ScrimManager {
+    private final ScrimController mBouncerScrimController;
+    private final ScrimController mBouncerlessScrimController;
+    private final KeyguardStateController mKeyguardStateController;
+    private final Executor mExecutor;
+
+    private ScrimController mCurrentController;
+    private final HashSet<Callback> mCallbacks;
+
+    /**
+     * Interface implemented for receiving updates to the active {@link ScrimController}.
+     */
+    public interface Callback {
+        /**
+         * Invoked when the controller changes.
+         * @param controller The currently active {@link ScrimController}.
+         */
+        void onScrimControllerChanged(ScrimController controller);
+    }
+
+    private final KeyguardStateController.Callback mKeyguardStateCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onKeyguardShowingChanged() {
+                    mExecutor.execute(() -> updateController());
+                }
+            };
+
+    @Inject
+    ScrimManager(@Main Executor executor,
+            @Named(BOUNCER_SCRIM_CONTROLLER) ScrimController bouncerScrimController,
+            @Named(BOUNCERLESS_SCRIM_CONTROLLER)ScrimController bouncerlessScrimController,
+            KeyguardStateController keyguardStateController) {
+        mExecutor = executor;
+        mCallbacks = new HashSet<>();
+        mBouncerlessScrimController = bouncerlessScrimController;
+        mBouncerScrimController = bouncerScrimController;
+        mKeyguardStateController = keyguardStateController;
+
+        mKeyguardStateController.addCallback(mKeyguardStateCallback);
+        updateController();
+    }
+
+    private void updateController() {
+        final ScrimController existingController = mCurrentController;
+        mCurrentController =  mKeyguardStateController.canDismissLockScreen()
+                ? mBouncerlessScrimController
+                : mBouncerScrimController;
+
+        if (existingController == mCurrentController) {
+            return;
+        }
+
+        mCallbacks.forEach(callback -> callback.onScrimControllerChanged(mCurrentController));
+    }
+
+    /**
+     * Adds a {@link Callback} to receive future changes to the active {@link ScrimController}.
+     */
+    public void addCallback(Callback callback) {
+        mExecutor.execute(() -> mCallbacks.add(callback));
+    }
+
+    /**
+     * Removes the {@link Callback} from receiving further updates.
+     */
+    public void removeCallback(Callback callback) {
+        mExecutor.execute(() -> mCallbacks.remove(callback));
+    }
+
+    /**
+     * Returns the currently get {@link ScrimController}.
+     * @return
+     */
+    public ScrimController getCurrentController() {
+        return mCurrentController;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java
new file mode 100644
index 0000000..40bc0ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java
@@ -0,0 +1,51 @@
+/*
+ * 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.systemui.dreams.touch.scrim.dagger;
+
+import com.android.systemui.dreams.touch.scrim.BouncerScrimController;
+import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
+import com.android.systemui.dreams.touch.scrim.ScrimController;
+
+import javax.inject.Named;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module for scrim related dependencies.
+ */
+@Module
+public interface ScrimModule {
+    String BOUNCERLESS_SCRIM_CONTROLLER = "bouncerless_scrim_controller";
+    String BOUNCER_SCRIM_CONTROLLER = "bouncer_scrim_controller";
+
+    /** */
+    @Provides
+    @Named(BOUNCERLESS_SCRIM_CONTROLLER)
+    static ScrimController providesBouncerlessScrimController(
+            BouncerlessScrimController controller) {
+        return controller;
+    }
+
+    /** */
+    @Provides
+    @Named(BOUNCER_SCRIM_CONTROLLER)
+    static ScrimController providesBouncerScrimController(
+            BouncerScrimController controller) {
+        return controller;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index d1a14a1..4bac697 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -19,7 +19,7 @@
 import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS;
 import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
 import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
-import static com.android.systemui.flags.FlagManager.EXTRA_ID;
+import static com.android.systemui.flags.FlagManager.EXTRA_NAME;
 import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
 import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
 
@@ -39,6 +39,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.jetbrains.annotations.NotNull;
@@ -72,14 +73,15 @@
 
     private final FlagManager mFlagManager;
     private final Context mContext;
+    private final GlobalSettings mGlobalSettings;
     private final SecureSettings mSecureSettings;
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
     private final ServerFlagReader mServerFlagReader;
-    private final Map<Integer, Flag<?>> mAllFlags;
-    private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
-    private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
-    private final Map<Integer, Integer> mIntFlagCache = new TreeMap<>();
+    private final Map<String, Flag<?>> mAllFlags;
+    private final Map<String, Boolean> mBooleanFlagCache = new TreeMap<>();
+    private final Map<String, String> mStringFlagCache = new TreeMap<>();
+    private final Map<String, Integer> mIntFlagCache = new TreeMap<>();
     private final Restarter mRestarter;
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
@@ -94,14 +96,16 @@
     public FeatureFlagsDebug(
             FlagManager flagManager,
             Context context,
+            GlobalSettings globalSettings,
             SecureSettings secureSettings,
             SystemPropertiesHelper systemProperties,
             @Main Resources resources,
             ServerFlagReader serverFlagReader,
-            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+            @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
             Restarter restarter) {
         mFlagManager = flagManager;
         mContext = context;
+        mGlobalSettings = globalSettings;
         mSecureSettings = secureSettings;
         mResources = resources;
         mSystemProperties = systemProperties;
@@ -133,96 +137,103 @@
     }
 
     private boolean isEnabledInternal(@NotNull BooleanFlag flag) {
-        int id = flag.getId();
-        if (!mBooleanFlagCache.containsKey(id)) {
-            mBooleanFlagCache.put(id,
+        String name = flag.getName();
+        if (!mBooleanFlagCache.containsKey(name)) {
+            mBooleanFlagCache.put(name,
                     readBooleanFlagInternal(flag, flag.getDefault()));
         }
 
-        return mBooleanFlagCache.get(id);
+        return mBooleanFlagCache.get(name);
     }
 
     @Override
     public boolean isEnabled(@NonNull ResourceBooleanFlag flag) {
-        int id = flag.getId();
-        if (!mBooleanFlagCache.containsKey(id)) {
-            mBooleanFlagCache.put(id,
+        String name = flag.getName();
+        if (!mBooleanFlagCache.containsKey(name)) {
+            mBooleanFlagCache.put(name,
                     readBooleanFlagInternal(flag, mResources.getBoolean(flag.getResourceId())));
         }
 
-        return mBooleanFlagCache.get(id);
+        return mBooleanFlagCache.get(name);
     }
 
     @Override
     public boolean isEnabled(@NonNull SysPropBooleanFlag flag) {
-        int id = flag.getId();
-        if (!mBooleanFlagCache.containsKey(id)) {
+        String name = flag.getName();
+        if (!mBooleanFlagCache.containsKey(name)) {
             // Use #readFlagValue to get the default. That will allow it to fall through to
             // teamfood if need be.
             mBooleanFlagCache.put(
-                    id,
+                    name,
                     mSystemProperties.getBoolean(
                             flag.getName(),
                             readBooleanFlagInternal(flag, flag.getDefault())));
         }
 
-        return mBooleanFlagCache.get(id);
+        return mBooleanFlagCache.get(name);
     }
 
     @NonNull
     @Override
     public String getString(@NonNull StringFlag flag) {
-        int id = flag.getId();
-        if (!mStringFlagCache.containsKey(id)) {
-            mStringFlagCache.put(id,
-                    readFlagValueInternal(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
+        String name = flag.getName();
+        if (!mStringFlagCache.containsKey(name)) {
+            mStringFlagCache.put(name,
+                    readFlagValueInternal(
+                            flag.getId(), name, flag.getDefault(), StringFlagSerializer.INSTANCE));
         }
 
-        return mStringFlagCache.get(id);
+        return mStringFlagCache.get(name);
     }
 
     @NonNull
     @Override
     public String getString(@NonNull ResourceStringFlag flag) {
-        int id = flag.getId();
-        if (!mStringFlagCache.containsKey(id)) {
-            mStringFlagCache.put(id,
-                    readFlagValueInternal(id, mResources.getString(flag.getResourceId()),
+        String name = flag.getName();
+        if (!mStringFlagCache.containsKey(name)) {
+            mStringFlagCache.put(name,
+                    readFlagValueInternal(
+                            flag.getId(), name, mResources.getString(flag.getResourceId()),
                             StringFlagSerializer.INSTANCE));
         }
 
-        return mStringFlagCache.get(id);
+        return mStringFlagCache.get(name);
     }
 
 
     @NonNull
     @Override
     public int getInt(@NonNull IntFlag flag) {
-        int id = flag.getId();
-        if (!mIntFlagCache.containsKey(id)) {
-            mIntFlagCache.put(id,
-                    readFlagValueInternal(id, flag.getDefault(), IntFlagSerializer.INSTANCE));
+        String name = flag.getName();
+        if (!mIntFlagCache.containsKey(name)) {
+            mIntFlagCache.put(name,
+                    readFlagValueInternal(
+                            flag.getId(), name, flag.getDefault(), IntFlagSerializer.INSTANCE));
         }
 
-        return mIntFlagCache.get(id);
+        return mIntFlagCache.get(name);
     }
 
     @NonNull
     @Override
     public int getInt(@NonNull ResourceIntFlag flag) {
-        int id = flag.getId();
-        if (!mIntFlagCache.containsKey(id)) {
-            mIntFlagCache.put(id,
-                    readFlagValueInternal(id, mResources.getInteger(flag.getResourceId()),
+        String name = flag.getName();
+        if (!mIntFlagCache.containsKey(name)) {
+            mIntFlagCache.put(name,
+                    readFlagValueInternal(
+                            flag.getId(), name, mResources.getInteger(flag.getResourceId()),
                             IntFlagSerializer.INSTANCE));
         }
 
-        return mIntFlagCache.get(id);
+        return mIntFlagCache.get(name);
     }
 
     /** Specific override for Boolean flags that checks against the teamfood list.*/
     private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) {
-        Boolean result = readBooleanFlagOverride(flag.getId());
+        Boolean result = readBooleanFlagOverride(flag.getName());
+        if (result == null) {
+            result = readBooleanFlagOverride(flag.getId());
+        }
         boolean hasServerOverride = mServerFlagReader.hasOverride(
                 flag.getNamespace(), flag.getName());
 
@@ -231,7 +242,7 @@
         if (!hasServerOverride
                 && !defaultValue
                 && result == null
-                && flag.getId() != Flags.TEAMFOOD.getId()
+                && !flag.getName().equals(Flags.TEAMFOOD.getName())
                 && flag.getTeamfood()) {
             return isEnabled(Flags.TEAMFOOD);
         }
@@ -244,16 +255,31 @@
         return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
     }
 
+    private Boolean readBooleanFlagOverride(String name) {
+        return readFlagValueInternal(name, BooleanFlagSerializer.INSTANCE);
+    }
+
+    // TODO(b/265188950): Remove id from this method once ids are fully deprecated.
     @NonNull
     private <T> T readFlagValueInternal(
-            int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
+            int id, String name, @NonNull T defaultValue, FlagSerializer<T> serializer) {
         requireNonNull(defaultValue, "defaultValue");
-        T result = readFlagValueInternal(id, serializer);
-        return result == null ? defaultValue : result;
+        T resultForName = readFlagValueInternal(name, serializer);
+        if (resultForName == null) {
+            T resultForId = readFlagValueInternal(id, serializer);
+            if (resultForId == null) {
+                return defaultValue;
+            } else {
+                setFlagValue(name, resultForId, serializer);
+                return resultForId;
+            }
+        }
+        return resultForName;
     }
 
 
     /** Returns the stored value or null if not set. */
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     @Nullable
     private <T> T readFlagValueInternal(int id, FlagSerializer<T> serializer) {
         try {
@@ -264,51 +290,71 @@
         return null;
     }
 
-    private <T> void setFlagValue(int id, @NonNull T value, FlagSerializer<T> serializer) {
+    /** Returns the stored value or null if not set. */
+    @Nullable
+    private <T> T readFlagValueInternal(String name, FlagSerializer<T> serializer) {
+        try {
+            return mFlagManager.readFlagValue(name, serializer);
+        } catch (Exception e) {
+            eraseInternal(name);
+        }
+        return null;
+    }
+
+    private <T> void setFlagValue(String name, @NonNull T value, FlagSerializer<T> serializer) {
         requireNonNull(value, "Cannot set a null value");
-        T currentValue = readFlagValueInternal(id, serializer);
+        T currentValue = readFlagValueInternal(name, serializer);
         if (Objects.equals(currentValue, value)) {
-            Log.i(TAG, "Flag id " + id + " is already " + value);
+            Log.i(TAG, "Flag id " + name + " is already " + value);
             return;
         }
         final String data = serializer.toSettingsData(value);
         if (data == null) {
-            Log.w(TAG, "Failed to set id " + id + " to " + value);
+            Log.w(TAG, "Failed to set id " + name + " to " + value);
             return;
         }
-        mSecureSettings.putStringForUser(mFlagManager.idToSettingsKey(id), data,
+        mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data,
                 UserHandle.USER_CURRENT);
-        Log.i(TAG, "Set id " + id + " to " + value);
-        removeFromCache(id);
-        mFlagManager.dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
+        Log.i(TAG, "Set id " + name + " to " + value);
+        removeFromCache(name);
+        mFlagManager.dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
     }
 
     <T> void eraseFlag(Flag<T> flag) {
         if (flag instanceof SysPropFlag) {
             mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
-            dispatchListenersAndMaybeRestart(flag.getId(), this::restartAndroid);
+            dispatchListenersAndMaybeRestart(flag.getName(), this::restartAndroid);
         } else {
-            eraseFlag(flag.getId());
+            eraseFlag(flag.getName());
         }
     }
 
     /** Erase a flag's overridden value if there is one. */
-    private void eraseFlag(int id) {
-        eraseInternal(id);
-        removeFromCache(id);
-        dispatchListenersAndMaybeRestart(id, this::restartSystemUI);
+    private void eraseFlag(String name) {
+        eraseInternal(name);
+        removeFromCache(name);
+        dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
     }
 
-    private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
-        mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
+    private void dispatchListenersAndMaybeRestart(String name, Consumer<Boolean> restartAction) {
+        mFlagManager.dispatchListenersAndMaybeRestart(name, restartAction);
     }
 
-    /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
+    /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
+    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     private void eraseInternal(int id) {
-        // We can't actually "erase" things from sysprops, but we can set them to empty!
-        mSecureSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "",
+        // We can't actually "erase" things from settings, but we can set them to empty!
+        mGlobalSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "",
                 UserHandle.USER_CURRENT);
-        Log.i(TAG, "Erase id " + id);
+        Log.i(TAG, "Erase name " + id);
+    }
+
+    /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
+    private void eraseInternal(String name) {
+        // We can't actually "erase" things from settings, but we can set them to empty!
+        mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "",
+                UserHandle.USER_CURRENT);
+        Log.i(TAG, "Erase name " + name);
     }
 
     @Override
@@ -339,13 +385,13 @@
 
     void setBooleanFlagInternal(Flag<?> flag, boolean value) {
         if (flag instanceof BooleanFlag) {
-            setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE);
         } else if (flag instanceof ResourceBooleanFlag) {
-            setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE);
         } else if (flag instanceof SysPropBooleanFlag) {
             // Store SysProp flags in SystemProperties where they can read by outside parties.
             mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
-            dispatchListenersAndMaybeRestart(flag.getId(),
+            dispatchListenersAndMaybeRestart(flag.getName(),
                     FeatureFlagsDebug.this::restartAndroid);
         } else {
             throw new IllegalArgumentException("Unknown flag type");
@@ -354,9 +400,9 @@
 
     void setStringFlagInternal(Flag<?> flag, String value) {
         if (flag instanceof StringFlag) {
-            setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, StringFlagSerializer.INSTANCE);
         } else if (flag instanceof ResourceStringFlag) {
-            setFlagValue(flag.getId(), value, StringFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, StringFlagSerializer.INSTANCE);
         } else {
             throw new IllegalArgumentException("Unknown flag type");
         }
@@ -364,9 +410,9 @@
 
     void setIntFlagInternal(Flag<?> flag, int value) {
         if (flag instanceof IntFlag) {
-            setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, IntFlagSerializer.INSTANCE);
         } else if (flag instanceof ResourceIntFlag) {
-            setFlagValue(flag.getId(), value, IntFlagSerializer.INSTANCE);
+            setFlagValue(flag.getName(), value, IntFlagSerializer.INSTANCE);
         } else {
             throw new IllegalArgumentException("Unknown flag type");
         }
@@ -405,17 +451,17 @@
                 Log.w(TAG, "No extras");
                 return;
             }
-            int id = extras.getInt(EXTRA_ID);
-            if (id <= 0) {
-                Log.w(TAG, "ID not set or less than  or equal to 0: " + id);
+            String name = extras.getString(EXTRA_NAME);
+            if (name == null || name.isEmpty()) {
+                Log.w(TAG, "NAME not set or is empty: " + name);
                 return;
             }
 
-            if (!mAllFlags.containsKey(id)) {
-                Log.w(TAG, "Tried to set unknown id: " + id);
+            if (!mAllFlags.containsKey(name)) {
+                Log.w(TAG, "Tried to set unknown name: " + name);
                 return;
             }
-            Flag<?> flag = mAllFlags.get(id);
+            Flag<?> flag = mAllFlags.get(name);
 
             if (!extras.containsKey(EXTRA_VALUE)) {
                 eraseFlag(flag);
@@ -452,13 +498,16 @@
 
             if (f instanceof ReleasedFlag) {
                 enabled = isEnabled((ReleasedFlag) f);
-                overridden = readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null
+                            || readBooleanFlagOverride(f.getId()) != null;
             } else if (f instanceof UnreleasedFlag) {
                 enabled = isEnabled((UnreleasedFlag) f);
-                overridden = readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null
+                            || readBooleanFlagOverride(f.getId()) != null;
             } else if (f instanceof ResourceBooleanFlag) {
                 enabled = isEnabled((ResourceBooleanFlag) f);
-                overridden = readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null
+                            || readBooleanFlagOverride(f.getId()) != null;
             } else if (f instanceof SysPropBooleanFlag) {
                 // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
                 enabled = isEnabled((SysPropBooleanFlag) f);
@@ -480,9 +529,9 @@
         }
     };
 
-    private void removeFromCache(int id) {
-        mBooleanFlagCache.remove(id);
-        mStringFlagCache.remove(id);
+    private void removeFromCache(String name) {
+        mBooleanFlagCache.remove(name);
+        mStringFlagCache.remove(name);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index b94d781..dc7fc28 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.InitializationChecker
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -34,7 +35,8 @@
     private val commandRegistry: CommandRegistry,
     private val flagCommand: FlagCommand,
     private val featureFlags: FeatureFlagsDebug,
-    private val broadcastSender: BroadcastSender
+    private val broadcastSender: BroadcastSender,
+    private val initializationChecker: InitializationChecker
 ) : CoreStartable {
 
     init {
@@ -46,8 +48,11 @@
     override fun start() {
         featureFlags.init()
         commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
-        val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
-        broadcastSender.sendBroadcast(intent)
+        if (initializationChecker.initializeComponents()) {
+            // protected broadcast should only be sent for the main process
+            val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
+            broadcastSender.sendBroadcast(intent)
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 8bddacc..7e14237 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -21,18 +21,16 @@
 import static java.util.Objects.requireNonNull;
 
 import android.content.res.Resources;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.DeviceConfigProxy;
 
 import org.jetbrains.annotations.NotNull;
 
 import java.io.PrintWriter;
+import java.util.HashMap;
 import java.util.Map;
 
 import javax.inject.Inject;
@@ -50,12 +48,11 @@
 
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
-    private final DeviceConfigProxy mDeviceConfigProxy;
     private final ServerFlagReader mServerFlagReader;
     private final Restarter mRestarter;
-    private final Map<Integer, Flag<?>> mAllFlags;
-    SparseBooleanArray mBooleanCache = new SparseBooleanArray();
-    SparseArray<String> mStringCache = new SparseArray<>();
+    private final Map<String, Flag<?>> mAllFlags;
+    private final Map<String, Boolean> mBooleanCache = new HashMap<>();
+    private final Map<String, String> mStringCache = new HashMap<>();
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
             new ServerFlagReader.ChangeListener() {
@@ -69,13 +66,11 @@
     public FeatureFlagsRelease(
             @Main Resources resources,
             SystemPropertiesHelper systemProperties,
-            DeviceConfigProxy deviceConfigProxy,
             ServerFlagReader serverFlagReader,
-            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+            @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
             Restarter restarter) {
         mResources = resources;
         mSystemProperties = systemProperties;
-        mDeviceConfigProxy = deviceConfigProxy;
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
         mRestarter = restarter;
@@ -106,50 +101,48 @@
 
     @Override
     public boolean isEnabled(ResourceBooleanFlag flag) {
-        int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
-        if (cacheIndex < 0) {
-            return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId()));
+        if (!mBooleanCache.containsKey(flag.getName())) {
+            return isEnabled(flag.getName(), mResources.getBoolean(flag.getResourceId()));
         }
 
-        return mBooleanCache.valueAt(cacheIndex);
+        return mBooleanCache.get(flag.getName());
     }
 
     @Override
     public boolean isEnabled(SysPropBooleanFlag flag) {
-        int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
-        if (cacheIndex < 0) {
+        if (!mBooleanCache.containsKey(flag.getName())) {
             return isEnabled(
-                    flag.getId(), mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
+                    flag.getName(),
+                    mSystemProperties.getBoolean(flag.getName(), flag.getDefault()));
         }
 
-        return mBooleanCache.valueAt(cacheIndex);
+        return mBooleanCache.get(flag.getName());
     }
 
-    private boolean isEnabled(int key, boolean defaultValue) {
-        mBooleanCache.append(key, defaultValue);
+    private boolean isEnabled(String name, boolean defaultValue) {
+        mBooleanCache.put(name, defaultValue);
         return defaultValue;
     }
 
     @NonNull
     @Override
     public String getString(@NonNull StringFlag flag) {
-        return getString(flag.getId(), flag.getDefault());
+        return getString(flag.getName(), flag.getDefault());
     }
 
     @NonNull
     @Override
     public String getString(@NonNull ResourceStringFlag flag) {
-        int cacheIndex = mStringCache.indexOfKey(flag.getId());
-        if (cacheIndex < 0) {
-            return getString(flag.getId(),
+        if (!mStringCache.containsKey(flag.getName())) {
+            return getString(flag.getName(),
                     requireNonNull(mResources.getString(flag.getResourceId())));
         }
 
-        return mStringCache.valueAt(cacheIndex);
+        return mStringCache.get(flag.getName());
     }
 
-    private String getString(int key, String defaultValue) {
-        mStringCache.append(key, defaultValue);
+    private String getString(String name, String defaultValue) {
+        mStringCache.put(name, defaultValue);
         return defaultValue;
     }
 
@@ -169,11 +162,17 @@
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: false");
         Map<String, Flag<?>> knownFlags = FlagsFactory.INSTANCE.getKnownFlags();
+        pw.println("Booleans: ");
         for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) {
             Flag<?> flag = nameToFlag.getValue();
-            int id = flag.getId();
+            if (!(flag instanceof BooleanFlag)
+                    || !(flag instanceof ResourceBooleanFlag)
+                    || !(flag instanceof SysPropBooleanFlag)) {
+                continue;
+            }
+
             boolean def = false;
-            if (mBooleanCache.indexOfKey(flag.getId()) < 0) {
+            if (!mBooleanCache.containsKey(flag.getName())) {
                 if (flag instanceof SysPropBooleanFlag) {
                     SysPropBooleanFlag f = (SysPropBooleanFlag) flag;
                     def = mSystemProperties.getBoolean(f.getName(), f.getDefault());
@@ -185,15 +184,32 @@
                     def = f.getDefault();
                 }
             }
-            pw.println("  sysui_flag_" + id + ": " + (mBooleanCache.get(id, def)));
+            pw.println(
+                    "  " + flag.getName() + ": "
+                            + (mBooleanCache.getOrDefault(flag.getName(), def)));
         }
-        int numStrings = mStringCache.size();
-        pw.println("Strings: " + numStrings);
-        for (int i = 0; i < numStrings; i++) {
-            final int id = mStringCache.keyAt(i);
-            final String value = mStringCache.valueAt(i);
-            final int length = value.length();
-            pw.println("  sysui_flag_" + id + ": [length=" + length + "] \"" + value + "\"");
+
+        pw.println("Strings: ");
+        for (Map.Entry<String, Flag<?>> nameToFlag : knownFlags.entrySet()) {
+            Flag<?> flag = nameToFlag.getValue();
+            if (!(flag instanceof StringFlag)
+                    || !(flag instanceof ResourceStringFlag)) {
+                continue;
+            }
+
+            String def = "";
+            if (!mBooleanCache.containsKey(flag.getName())) {
+                if (flag instanceof ResourceStringFlag) {
+                    ResourceStringFlag f = (ResourceStringFlag) flag;
+                    def = mResources.getString(f.getResourceId());
+                } else if (flag instanceof StringFlag) {
+                    StringFlag f = (StringFlag) flag;
+                    def = f.getDefault();
+                }
+            }
+            String value = mStringCache.getOrDefault(flag.getName(), def);
+            pw.println(
+                    "  " + flag.getName() + ": [length=" + value.length() + "] \"" + value + "\"");
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index b7fc0e4..daf9429 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -39,12 +39,12 @@
     private final List<String> mOffCommands = List.of("false", "off", "0", "disable");
     private final List<String> mSetCommands = List.of("set", "put");
     private final FeatureFlagsDebug mFeatureFlags;
-    private final Map<Integer, Flag<?>> mAllFlags;
+    private final Map<String, Flag<?>> mAllFlags;
 
     @Inject
     FlagCommand(
             FeatureFlagsDebug featureFlags,
-            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+            @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags
     ) {
         mFeatureFlags = featureFlags;
         mAllFlags = allFlags;
@@ -53,30 +53,22 @@
     @Override
     public void execute(@NonNull PrintWriter pw, @NonNull List<String> args) {
         if (args.size() == 0) {
-            pw.println("Error: no flag id supplied");
+            pw.println("Error: no flag name supplied");
             help(pw);
             pw.println();
             printKnownFlags(pw);
             return;
         }
 
-        int id = 0;
-        try {
-            id = Integer.parseInt(args.get(0));
-            if (!mAllFlags.containsKey(id)) {
-                pw.println("Unknown flag id: " + id);
-                pw.println();
-                printKnownFlags(pw);
-                return;
-            }
-        } catch (NumberFormatException e) {
-            id = flagNameToId(args.get(0));
-            if (id == 0) {
-                pw.println("Invalid flag. Must an integer id or flag name: " + args.get(0));
-                return;
-            }
+        String name = args.get(0);
+        if (!mAllFlags.containsKey(name)) {
+            pw.println("Unknown flag name: " + name);
+            pw.println();
+            printKnownFlags(pw);
+            return;
         }
-        Flag<?> flag = mAllFlags.get(id);
+
+        Flag<?> flag = mAllFlags.get(name);
 
         String cmd = "";
         if (args.size() > 1) {
@@ -117,7 +109,7 @@
                 return;
             }
 
-            pw.println("Flag " + id + " is " + newValue);
+            pw.println("Flag " + name + " is " + newValue);
             pw.flush();  // Next command will restart sysui, so flush before we do so.
             if (shouldSet) {
                 mFeatureFlags.setBooleanFlagInternal(flag, newValue);
@@ -136,11 +128,11 @@
                     return;
                 }
                 String value = args.get(2);
-                pw.println("Setting Flag " + id + " to " + value);
+                pw.println("Setting Flag " + name + " to " + value);
                 pw.flush();  // Next command will restart sysui, so flush before we do so.
                 mFeatureFlags.setStringFlagInternal(flag, args.get(2));
             } else {
-                pw.println("Flag " + id + " is " + getStringFlag(flag));
+                pw.println("Flag " + name + " is " + getStringFlag(flag));
             }
             return;
         } else if (isIntFlag(flag)) {
@@ -155,11 +147,11 @@
                     return;
                 }
                 int value = Integer.parseInt(args.get(2));
-                pw.println("Setting Flag " + id + " to " + value);
+                pw.println("Setting Flag " + name + " to " + value);
                 pw.flush();  // Next command will restart sysui, so flush before we do so.
                 mFeatureFlags.setIntFlagInternal(flag, value);
             } else {
-                pw.println("Flag " + id + " is " + getIntFlag(flag));
+                pw.println("Flag " + name + " is " + getIntFlag(flag));
             }
             return;
         }
@@ -182,8 +174,7 @@
     private boolean isBooleanFlag(Flag<?> flag) {
         return (flag instanceof BooleanFlag)
                 || (flag instanceof ResourceBooleanFlag)
-                || (flag instanceof SysPropFlag)
-                || (flag instanceof DeviceConfigBooleanFlag);
+                || (flag instanceof SysPropFlag);
     }
 
     private boolean isBooleanFlagEnabled(Flag<?> flag) {
@@ -252,15 +243,14 @@
         for (int i = 0; i < longestFieldName - "Flag Name".length() + 1; i++) {
             pw.print(" ");
         }
-        pw.println("ID   Value");
+        pw.println(" Value");
         for (int i = 0; i < longestFieldName; i++) {
             pw.print("=");
         }
-        pw.println(" ==== ========");
+        pw.println(" ========");
         for (String fieldName : fields.keySet()) {
             Flag<?> flag = fields.get(fieldName);
-            int id = flag.getId();
-            if (id == 0 || !mAllFlags.containsKey(id)) {
+            if (!mAllFlags.containsKey(flag.getName())) {
                 continue;
             }
             pw.print(fieldName);
@@ -268,9 +258,9 @@
             for (int i = 0; i < longestFieldName - fieldWidth + 1; i++) {
                 pw.print(" ");
             }
-            pw.printf("%-4d ", id);
+            pw.print(" ");
             if (isBooleanFlag(flag)) {
-                pw.println(isBooleanFlagEnabled(mAllFlags.get(id)));
+                pw.println(isBooleanFlagEnabled(flag));
             } else if (isStringFlag(flag)) {
                 pw.println(getStringFlag(flag));
             } else if (isIntFlag(flag)) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ae8a9d0..748b5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -64,6 +64,9 @@
     // TODO(b/259130119): Tracking Bug
     val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true)
 
+    // TODO(b/265804648): Tracking Bug
+    @JvmField val DISABLE_FSI = unreleasedFlag(265804648, "disable_fsi")
+
     // TODO(b/254512538): Tracking Bug
     val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true)
 
@@ -77,12 +80,6 @@
     // TODO(b/254512731): Tracking Bug
     @JvmField val NOTIFICATION_DISMISSAL_FADE = releasedFlag(113, "notification_dismissal_fade")
 
-    // TODO(b/259558771): Tracking Bug
-    val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix")
-
-    // TODO(b/259559750): Tracking Bug
-    val SEMI_STABLE_SORT = releasedFlag(115, "semi_stable_sort")
-
     @JvmField val USE_ROUNDNESS_SOURCETYPES = releasedFlag(116, "use_roundness_sourcetype")
 
     // TODO(b/259217907)
@@ -106,7 +103,11 @@
         unreleasedFlag(174148361, "notification_inline_reply_animation", teamfood = true)
 
     val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
-        unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
+        releasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
+
+    // TODO(b/263414400): Tracking Bug
+    @JvmField
+    val NOTIFICATION_ANIMATE_BIG_PICTURE = unreleasedFlag(120, "notification_animate_big_picture")
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -127,13 +128,6 @@
     val LOCKSCREEN_CUSTOM_CLOCKS = unreleasedFlag(207, "lockscreen_custom_clocks", teamfood = true)
 
     /**
-     * Flag to enable the usage of the new bouncer data source. This is a refactor of and eventual
-     * replacement of KeyguardBouncer.java.
-     */
-    // TODO(b/254512385): Tracking Bug
-    @JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer")
-
-    /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
      */
@@ -181,7 +175,7 @@
      * of the Alternate/Authentication Bouncer. No visual UI changes.
      */
     // TODO(b/260619425): Tracking Bug
-    @JvmField val MODERN_ALTERNATE_BOUNCER = unreleasedFlag(219, "modern_alternate_bouncer")
+    @JvmField val MODERN_ALTERNATE_BOUNCER = releasedFlag(219, "modern_alternate_bouncer")
 
     /** Flag to control the migration of face auth to modern architecture. */
     // TODO(b/262838215): Tracking bug
@@ -197,8 +191,7 @@
 
     /** A different path for unocclusion transitions back to keyguard */
     // TODO(b/262859270): Tracking Bug
-    @JvmField
-    val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = true)
+    @JvmField val UNOCCLUSION_TRANSITION = releasedFlag(223, "unocclusion_transition")
 
     // flag for controlling auto pin confirmation and material u shapes in bouncer
     @JvmField
@@ -214,6 +207,20 @@
     val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS =
         unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = false)
 
+    // TODO(b/242908637): Tracking Bug
+    @JvmField
+    val WALLPAPER_FULLSCREEN_PREVIEW =
+        unreleasedFlag(227, "wallpaper_fullscreen_preview", teamfood = true)
+
+    /** Whether the long-press gesture to open wallpaper picker is enabled. */
+    // TODO(b/266242192): Tracking Bug
+    @JvmField
+    val LOCK_SCREEN_LONG_PRESS_ENABLED =
+        unreleasedFlag(
+            228,
+            "lock_screen_long_press_enabled",
+        )
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -223,11 +230,11 @@
     // TODO(b/254513100): Tracking Bug
     val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
         releasedFlag(401, "smartspace_shared_element_transition_enabled")
-    val SMARTSPACE = resourceBooleanFlag(402, R.bool.flag_smartspace, "smartspace")
 
     // TODO(b/258517050): Clean up after the feature is launched.
     @JvmField
-    val SMARTSPACE_DATE_WEATHER_DECOUPLED = unreleasedFlag(403, "smartspace_date_weather_decoupled")
+    val SMARTSPACE_DATE_WEATHER_DECOUPLED =
+        sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = false)
 
     // 500 - quick settings
 
@@ -260,10 +267,11 @@
     // 600- status bar
 
     // TODO(b/256614753): Tracking Bug
-    val NEW_STATUS_BAR_MOBILE_ICONS = unreleasedFlag(606, "new_status_bar_mobile_icons")
+    val NEW_STATUS_BAR_MOBILE_ICONS =
+        unreleasedFlag(606, "new_status_bar_mobile_icons", teamfood = true)
 
     // TODO(b/256614210): Tracking Bug
-    val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon")
+    val NEW_STATUS_BAR_WIFI_ICON = unreleasedFlag(607, "new_status_bar_wifi_icon", teamfood = true)
 
     // TODO(b/256614751): Tracking Bug
     val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND =
@@ -304,9 +312,7 @@
     val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")
 
     // 804 - monochromatic themes
-    @JvmField
-    val MONOCHROMATIC_THEMES =
-        sysPropBooleanFlag(804, "persist.sysui.monochromatic", default = false)
+    @JvmField val MONOCHROMATIC_THEME = unreleasedFlag(804, "monochromatic", teamfood = true)
 
     // 900 - media
     // TODO(b/254512697): Tracking Bug
@@ -330,8 +336,7 @@
     // TODO(b/254513168): Tracking Bug
     @JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
 
-    @JvmField
-    val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media", teamfood = true)
+    @JvmField val MEDIA_FALSING_PENALTY = releasedFlag(908, "media_falsing_media")
 
     // TODO(b/261734857): Tracking Bug
     @JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
@@ -343,6 +348,23 @@
     // TODO(b/263512203): Tracking Bug
     val MEDIA_EXPLICIT_INDICATOR = unreleasedFlag(911, "media_explicit_indicator", teamfood = true)
 
+    // TODO(b/265813373): Tracking Bug
+    val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE =
+        unreleasedFlag(912, "media_ttt_dismiss_gesture", teamfood = true)
+
+    // TODO(b/266157412): Tracking Bug
+    val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
+
+    // TODO(b/266739309): Tracking Bug
+    @JvmField
+    val MEDIA_RECOMMENDATION_CARD_UPDATE = unreleasedFlag(914, "media_recommendation_card_update")
+
+    // TODO(b/267007629): Tracking Bug
+    val MEDIA_RESUME_PROGRESS = unreleasedFlag(915, "media_resume_progress")
+
+    // TODO(b/267166152) : Tracking Bug
+    val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations")
+
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
 
@@ -424,6 +446,18 @@
             teamfood = false
         )
 
+    // TODO(b/198643358): Tracking bug
+    @Keep
+    @JvmField
+    val ENABLE_PIP_SIZE_LARGE_SCREEN =
+        sysPropBooleanFlag(1114, "persist.wm.debug.enable_pip_size_large_screen", default = false)
+
+    // TODO(b/265998256): Tracking bug
+    @Keep
+    @JvmField
+    val ENABLE_PIP_APP_ICON_OVERLAY =
+        sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = false)
+
     // 1200 - predictive back
     @Keep
     @JvmField
@@ -433,7 +467,7 @@
     @Keep
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_ANIM =
-        sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = false)
+        sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = true)
 
     @Keep
     @JvmField
@@ -458,12 +492,33 @@
     val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM =
         unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
 
+    // TODO(b/238475428): Tracking Bug
+    @JvmField
+    val WM_SHADE_ALLOW_BACK_GESTURE =
+        sysPropBooleanFlag(1207, "persist.wm.debug.shade_allow_back_gesture", default = false)
+
+    // TODO(b/238475428): Tracking Bug
+    @JvmField
+    val WM_SHADE_ANIMATE_BACK_GESTURE =
+        unreleasedFlag(1208, "persist.wm.debug.shade_animate_back_gesture", teamfood = false)
+
+    // TODO(b/265639042): Tracking Bug
+    @JvmField
+    val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM =
+        unreleasedFlag(1209, "persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
+
     // 1300 - screenshots
     // TODO(b/254513155): Tracking Bug
     @JvmField
     val SCREENSHOT_WORK_PROFILE_POLICY =
         unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
 
+    // TODO(b/264916608): Tracking Bug
+    @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
+
+    // TODO(b/266955521): Tracking bug
+    @JvmField val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection")
+
     // 1400 - columbus
     // TODO(b/254512756): Tracking Bug
     val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
@@ -472,34 +527,49 @@
     val QUICK_TAP_FLOW_FRAMEWORK =
         unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
 
-    // 1500 - chooser
+    // 1500 - chooser aka sharesheet
     // TODO(b/254512507): Tracking Bug
     val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
 
+    // TODO(b/266983432) Tracking Bug
+    val SHARESHEET_CUSTOM_ACTIONS = unreleasedFlag(1501, "sharesheet_custom_actions")
+
+    // TODO(b/266982749) Tracking Bug
+    val SHARESHEET_RESELECTION_ACTION = unreleasedFlag(1502, "sharesheet_reselection_action")
+
+    // TODO(b/266983474) Tracking Bug
+    val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview")
+
     // 1600 - accessibility
     @JvmField
     val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
         unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
 
     // 1700 - clipboard
-    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
     @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
+    // TODO(b/267162944): Tracking bug
+    @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = unreleasedFlag(1702, "clipboard_data_model")
 
     // 1800 - shade container
     @JvmField
-    val LEAVE_SHADE_OPEN_FOR_BUGREPORT =
-        unreleasedFlag(1800, "leave_shade_open_for_bugreport", teamfood = true)
+    val LEAVE_SHADE_OPEN_FOR_BUGREPORT = releasedFlag(1800, "leave_shade_open_for_bugreport")
+    // TODO(b/265944639): Tracking Bug
+    @JvmField val DUAL_SHADE = releasedFlag(1801, "dual_shade")
 
     // 1900
     @JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
 
     // 2000 - device controls
-    @Keep @JvmField val USE_APP_PANELS = unreleasedFlag(2000, "use_app_panels", teamfood = true)
+    @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels", teamfood = true)
 
     @JvmField
     val APP_PANELS_ALL_APPS_ALLOWED =
         unreleasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true)
 
+    @JvmField
+    val CONTROLS_MANAGEMENT_NEW_FLOWS =
+        unreleasedFlag(2002, "controls_management_new_flows", teamfood = true)
+
     // 2100 - Falsing Manager
     @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
 
@@ -510,7 +580,7 @@
     @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
 
     // 2300 - stylus
-    @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
+    @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
     @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
     @JvmField
     val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications")
@@ -532,6 +602,22 @@
     @JvmField
     val OUTPUT_SWITCHER_DEVICE_STATUS = unreleasedFlag(2502, "output_switcher_device_status")
 
+    // TODO(b/20911786): Tracking Bug
+    @JvmField
+    val OUTPUT_SWITCHER_SHOW_API_ENABLED =
+        unreleasedFlag(2503, "output_switcher_show_api_enabled", teamfood = true)
+
+    // 2700 - unfold transitions
+    // TODO(b/265764985): Tracking Bug
+    @Keep
+    @JvmField
+    val ENABLE_DARK_VIGNETTE_WHEN_FOLDING =
+        unreleasedFlag(2700, "enable_dark_vignette_when_folding")
+
     // TODO(b259590361): Tracking bug
     val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
+
+    // 2600 - keyboard shortcut
+    // TODO(b/259352579): Tracking Bug
+    @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 8442230..0054d26 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -28,8 +28,8 @@
         @JvmStatic
         @Provides
         @Named(ALL_FLAGS)
-        fun providesAllFlags(): Map<Int, Flag<*>> {
-            return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap()
+        fun providesAllFlags(): Map<String, Flag<*>> {
+            return FlagsFactory.knownFlags
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index ae05c46..a02b795 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -54,10 +54,11 @@
                 return
             }
 
+
             for ((listener, flags) in listeners) {
                 propLoop@ for (propName in properties.keyset) {
                     for (flag in flags) {
-                        if (propName == getServerOverrideName(flag.id)) {
+                        if (propName == getServerOverrideName(flag.id) || propName == flag.name) {
                             listener.onChange()
                             break@propLoop
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
index 18fb423..98896b1 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java
@@ -37,9 +37,14 @@
     private final int mId;
     private String mOldClass;
 
-    private ExtensionFragmentListener(View view, String tag, int id, Extension<T> extension) {
+    private ExtensionFragmentListener(
+            FragmentService fragmentService,
+            View view,
+            String tag,
+            int id,
+            Extension<T> extension) {
         mTag = tag;
-        mFragmentHostManager = FragmentHostManager.get(view);
+        mFragmentHostManager = fragmentService.getFragmentHostManager(view);
         mExtension = extension;
         mId = id;
         mFragmentHostManager.getFragmentManager().beginTransaction()
@@ -61,8 +66,13 @@
         mExtension.clearItem(true);
     }
 
-    public static <T> void attachExtensonToFragment(View view, String tag, int id,
+    public static <T> void attachExtensonToFragment(
+            FragmentService fragmentService,
+            View view,
+            String tag,
+            int id,
             Extension<T> extension) {
-        extension.addCallback(new ExtensionFragmentListener(view, tag, id, extension));
+        extension.addCallback(
+                new ExtensionFragmentListener(fragmentService, view, tag, id, extension));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 9c7411b..6a27ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -36,7 +36,6 @@
 import androidx.annotation.NonNull;
 
 import com.android.settingslib.applications.InterestingConfigChanges;
-import com.android.systemui.Dependency;
 import com.android.systemui.plugins.Plugin;
 import com.android.systemui.util.leak.LeakDetector;
 
@@ -46,12 +45,17 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
 public class FragmentHostManager {
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Context mContext;
     private final HashMap<String, ArrayList<FragmentListener>> mListeners = new HashMap<>();
     private final View mRootView;
+    private final LeakDetector mLeakDetector;
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
             ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
                     | ActivityInfo.CONFIG_ASSETS_PATHS);
@@ -61,14 +65,24 @@
     private FragmentController mFragments;
     private FragmentLifecycleCallbacks mLifecycleCallbacks;
 
-    FragmentHostManager(FragmentService manager, View rootView) {
+    @AssistedInject
+    FragmentHostManager(
+            @Assisted View rootView,
+            FragmentService manager,
+            LeakDetector leakDetector) {
         mContext = rootView.getContext();
         mManager = manager;
         mRootView = rootView;
+        mLeakDetector = leakDetector;
         mConfigChanges.applyNewConfig(mContext.getResources());
         createFragmentHost(null);
     }
 
+    @AssistedFactory
+    public interface Factory {
+        FragmentHostManager create(View rootView);
+    }
+
     private void createFragmentHost(Parcelable savedState) {
         mFragments = FragmentController.createController(new HostCallbacks());
         mFragments.attachHost(null);
@@ -86,7 +100,7 @@
 
             @Override
             public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
-                Dependency.get(LeakDetector.class).trackGarbage(f);
+                mLeakDetector.trackGarbage(f);
             }
         };
         mFragments.getFragmentManager().registerFragmentLifecycleCallbacks(mLifecycleCallbacks,
@@ -211,19 +225,6 @@
         }
     }
 
-    public static FragmentHostManager get(View view) {
-        try {
-            return Dependency.get(FragmentService.class).getFragmentHostManager(view);
-        } catch (ClassCastException e) {
-            // TODO: Some auto handling here?
-            throw e;
-        }
-    }
-
-    public static void removeAndDestroy(View view) {
-        Dependency.get(FragmentService.class).removeAndDestroy(view);
-    }
-
     public void reloadFragments() {
         Trace.beginSection("FrargmentHostManager#reloadFragments");
         // Save the old state.
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index fe945fb..d302b13a 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -53,6 +53,7 @@
      */
     private final ArrayMap<String, FragmentInstantiationInfo> mInjectionMap = new ArrayMap<>();
     private final Handler mHandler = new Handler();
+    private final FragmentHostManager.Factory mFragmentHostManagerFactory;
 
     private ConfigurationController.ConfigurationListener mConfigurationListener =
             new ConfigurationController.ConfigurationListener() {
@@ -67,8 +68,10 @@
     @Inject
     public FragmentService(
             FragmentCreator.Factory fragmentCreatorFactory,
+            FragmentHostManager.Factory fragmentHostManagerFactory,
             ConfigurationController configurationController,
             DumpManager dumpManager) {
+        mFragmentHostManagerFactory = fragmentHostManagerFactory;
         addFragmentInstantiationProvider(fragmentCreatorFactory.build());
         configurationController.addCallback(mConfigurationListener);
 
@@ -152,7 +155,7 @@
 
         public FragmentHostState(View view) {
             mView = view;
-            mFragmentHostManager = new FragmentHostManager(FragmentService.this, mView);
+            mFragmentHostManager = mFragmentHostManagerFactory.create(mView);
         }
 
         public void sendConfigurationChange(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index db2cd91..e80e71c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -21,7 +21,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
-import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
@@ -901,7 +900,7 @@
                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                 intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
                         EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU);
-                mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+                mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
             }
         }
     }
@@ -959,8 +958,7 @@
             mHandler.postDelayed(new Runnable() {
                 @Override
                 public void run() {
-                    mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
-                            SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
+                    mScreenshotHelper.takeScreenshot(SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
                     mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                     mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 4f1a2b3..ad7973e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -24,7 +24,6 @@
 import android.bluetooth.le.ScanRecord;
 import android.bluetooth.le.ScanResult;
 import android.bluetooth.le.ScanSettings;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.hardware.input.InputManager;
@@ -53,6 +52,7 @@
 import com.android.systemui.CoreStartable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -108,6 +108,7 @@
     protected volatile Context mContext;
 
     private final Provider<LocalBluetoothManager> mBluetoothManagerProvider;
+    private final SecureSettings mSecureSettings;
 
     private boolean mEnabled;
     private String mKeyboardName;
@@ -125,9 +126,11 @@
     private int mState;
 
     @Inject
-    public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider) {
+    public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider,
+            SecureSettings secureSettings) {
         mContext = context;
         this.mBluetoothManagerProvider = bluetoothManagerProvider;
+        mSecureSettings = secureSettings;
     }
 
     @Override
@@ -298,9 +301,8 @@
     }
 
     private boolean isUserSetupComplete() {
-        ContentResolver resolver = mContext.getContentResolver();
-        return Secure.getIntForUser(
-                resolver, Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+        return mSecureSettings.getIntForUser(
+                Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
     }
 
     private CachedBluetoothDevice getPairedKeyboard() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 482138e..680c504 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -31,10 +31,12 @@
 import android.util.Log
 import com.android.systemui.SystemUIAppComponentFactoryBase
 import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.runBlocking
 
 class CustomizationProvider :
@@ -42,6 +44,7 @@
 
     @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
     @Inject lateinit var previewManager: KeyguardRemotePreviewManager
+    @Inject @Main lateinit var mainDispatcher: CoroutineDispatcher
 
     private lateinit var contextAvailableCallback: ContextAvailableCallback
 
@@ -138,12 +141,14 @@
         selectionArgs: Array<out String>?,
         sortOrder: String?,
     ): Cursor? {
-        return when (uriMatcher.match(uri)) {
-            MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() }
-            MATCH_CODE_ALL_SLOTS -> querySlots()
-            MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() }
-            MATCH_CODE_ALL_FLAGS -> queryFlags()
-            else -> null
+        return runBlocking(mainDispatcher) {
+            when (uriMatcher.match(uri)) {
+                MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
+                MATCH_CODE_ALL_SLOTS -> querySlots()
+                MATCH_CODE_ALL_SELECTIONS -> querySelections()
+                MATCH_CODE_ALL_FLAGS -> queryFlags()
+                else -> null
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index f4a1227..47872d2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -80,6 +79,7 @@
 import com.android.internal.policy.IKeyguardStateCallback;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -123,6 +123,7 @@
     private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
     private final ScreenOnCoordinator mScreenOnCoordinator;
     private final ShellTransitions mShellTransitions;
+    private final DisplayTracker mDisplayTracker;
 
     private static int newModeToLegacyMode(int newMode) {
         switch (newMode) {
@@ -286,12 +287,14 @@
     public KeyguardService(KeyguardViewMediator keyguardViewMediator,
                            KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
                            ScreenOnCoordinator screenOnCoordinator,
-                           ShellTransitions shellTransitions) {
+                           ShellTransitions shellTransitions,
+                           DisplayTracker displayTracker) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
         mScreenOnCoordinator = screenOnCoordinator;
         mShellTransitions = shellTransitions;
+        mDisplayTracker = displayTracker;
     }
 
     @Override
@@ -328,7 +331,7 @@
                         unoccludeAnimationAdapter);
             }
             ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
-                    DEFAULT_DISPLAY, definition);
+                    mDisplayTracker.getDefaultDisplayId(), definition);
             return;
         }
         if (sEnableRemoteKeyguardGoingAwayAnimation) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fe84ac5..95bba13 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1334,7 +1334,7 @@
         mHideAnimation = AnimationUtils.loadAnimation(mContext,
                 com.android.internal.R.anim.lock_screen_behind_enter);
 
-        mWorkLockController = new WorkLockActivityController(mContext);
+        mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
     }
 
     @Override
@@ -1912,12 +1912,6 @@
      * Enable the keyguard if the settings are appropriate.
      */
     private void doKeyguardLocked(Bundle options) {
-        if (KeyguardUpdateMonitor.CORE_APPS_ONLY) {
-            // Don't show keyguard during half-booted cryptkeeper stage.
-            if (DEBUG) Log.d(TAG, "doKeyguard: not showing because booting to cryptkeeper");
-            return;
-        }
-
         // if another app is disabling us, don't show
         if (!mExternallyEnabled) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
@@ -2287,6 +2281,10 @@
         }
         if (!mKeyguardDonePending && mHideAnimationRun && !mHideAnimationRunning) {
             handleKeyguardDone();
+        } else if (mSurfaceBehindRemoteAnimationRunning) {
+            // We're already running the keyguard exit animation, likely due to an in-progress swipe
+            // to unlock.
+           exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */);
         } else if (!mHideAnimationRun) {
             if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation");
             mHideAnimationRun = true;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index 16817ed..b92499e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -26,10 +26,10 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
@@ -37,16 +37,20 @@
     private static final String TAG = WorkLockActivityController.class.getSimpleName();
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final IActivityTaskManager mIatm;
 
-    public WorkLockActivityController(Context context) {
-        this(context, TaskStackChangeListeners.getInstance(), ActivityTaskManager.getService());
+    public WorkLockActivityController(Context context, UserTracker userTracker) {
+        this(context, userTracker, TaskStackChangeListeners.getInstance(),
+                ActivityTaskManager.getService());
     }
 
     @VisibleForTesting
     WorkLockActivityController(
-            Context context, TaskStackChangeListeners tscl, IActivityTaskManager iAtm) {
+            Context context, UserTracker userTracker, TaskStackChangeListeners tscl,
+            IActivityTaskManager iAtm) {
         mContext = context;
+        mUserTracker = userTracker;
         mIatm = iAtm;
 
         tscl.registerTaskStackListener(mLockListener);
@@ -65,7 +69,8 @@
         options.setLaunchTaskId(info.taskId);
         options.setTaskOverlay(true, false /* canResume */);
 
-        final int result = startActivityAsUser(intent, options.toBundle(), UserHandle.USER_CURRENT);
+        final int result = startActivityAsUser(intent, options.toBundle(),
+                mUserTracker.getUserId());
         if (ActivityManager.isStartResultSuccessful(result)) {
             // OK
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index 80c6130..faeb485 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.data
 
 import android.view.KeyEvent
+import android.window.OnBackAnimationCallback
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
 import java.lang.ref.WeakReference
@@ -51,4 +52,6 @@
         cancelAction: Runnable?,
     )
     fun willDismissWithActions(): Boolean
+    /** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
+    fun getBackCallback(): OnBackAnimationCallback
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index ea5b4f4..80675d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -25,10 +25,13 @@
 object BuiltInKeyguardQuickAffordanceKeys {
     // Please keep alphabetical order of const names to simplify future maintenance.
     const val CAMERA = "camera"
+    const val CREATE_NOTE = "create_note"
     const val DO_NOT_DISTURB = "do_not_disturb"
     const val FLASHLIGHT = "flashlight"
     const val HOME_CONTROLS = "home"
+    const val MUTE = "mute"
     const val QR_CODE_SCANNER = "qr_code_scanner"
     const val QUICK_ACCESS_WALLET = "wallet"
+    const val VIDEO_CAMERA = "video_camera"
     // Please keep alphabetical order of const names to simplify future maintenance.
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index dbc376e..5a9f775 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -19,6 +19,7 @@
 
 import android.app.StatusBarManager
 import android.content.Context
+import android.content.pm.PackageManager
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.camera.CameraGestureHelper
@@ -36,6 +37,7 @@
 @Inject
 constructor(
     @Application private val context: Context,
+    private val packageManager: PackageManager,
     private val cameraGestureHelper: Lazy<CameraGestureHelper>,
 ) : KeyguardQuickAffordanceConfig {
 
@@ -46,7 +48,7 @@
         get() = context.getString(R.string.accessibility_camera_button)
 
     override val pickerIconResourceId: Int
-        get() = com.android.internal.R.drawable.perm_group_camera
+        get() = R.drawable.ic_camera
 
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
         get() =
@@ -54,12 +56,20 @@
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                     icon =
                         Icon.Resource(
-                            com.android.internal.R.drawable.perm_group_camera,
+                            R.drawable.ic_camera,
                             ContentDescription.Resource(R.string.accessibility_camera_button)
                         )
                 )
             )
 
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+        return if (isLaunchable()) {
+            super.getPickerScreenState()
+        } else {
+            KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+        }
+    }
+
     override fun onTriggered(
         expandable: Expandable?
     ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
@@ -68,4 +78,8 @@
             .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
         return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
     }
+
+    private fun isLaunchable(): Boolean {
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index ed1ff32..be73f85 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -128,7 +128,7 @@
                 awaitClose { controller.removeCallback(callback) }
             },
             secureSettings
-                .observerFlow(Settings.Secure.ZEN_DURATION)
+                .observerFlow(userTracker.userId, Settings.Secure.ZEN_DURATION)
                 .onStart { emit(Unit) }
                 .map { secureSettings.getInt(Settings.Secure.ZEN_DURATION, ZEN_MODE_OFF) }
                 .flowOn(backgroundDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index 71d01eb..4556195 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -33,20 +33,24 @@
         @Provides
         @ElementsIntoSet
         fun quickAffordanceConfigs(
+            camera: CameraQuickAffordanceConfig,
             doNotDisturb: DoNotDisturbQuickAffordanceConfig,
             flashlight: FlashlightQuickAffordanceConfig,
             home: HomeControlsKeyguardQuickAffordanceConfig,
+            mute: MuteQuickAffordanceConfig,
             quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
             qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-            camera: CameraQuickAffordanceConfig,
+            videoCamera: VideoCameraQuickAffordanceConfig,
         ): Set<KeyguardQuickAffordanceConfig> {
             return setOf(
                 camera,
                 doNotDisturb,
                 flashlight,
                 home,
+                mute,
                 quickAccessWallet,
                 qrCodeScanner,
+                videoCamera,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
new file mode 100644
index 0000000..da91572
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 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.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+@SysUISingleton
+class MuteQuickAffordanceConfig @Inject constructor(
+        context: Context,
+        private val userTracker: UserTracker,
+        private val userFileManager: UserFileManager,
+        private val ringerModeTracker: RingerModeTracker,
+        private val audioManager: AudioManager,
+        @Application private val coroutineScope: CoroutineScope,
+        @Main private val mainDispatcher: CoroutineDispatcher,
+        @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceConfig {
+
+    private var previousNonSilentMode: Int = DEFAULT_LAST_NON_SILENT_VALUE
+
+    override val key: String = BuiltInKeyguardQuickAffordanceKeys.MUTE
+
+    override val pickerName: String = context.getString(R.string.volume_ringer_status_silent)
+
+    override val pickerIconResourceId: Int = R.drawable.ic_notifications_silence
+
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+        ringerModeTracker.ringerModeInternal.asFlow()
+            .onStart { getLastNonSilentRingerMode() }
+            .distinctUntilChanged()
+            .onEach { mode ->
+                // only remember last non-SILENT ringer mode
+                if (mode != null && mode != AudioManager.RINGER_MODE_SILENT) {
+                    previousNonSilentMode = mode
+                }
+            }
+            .map { mode ->
+                val (activationState, contentDescriptionRes) = when {
+                    audioManager.isVolumeFixed ->
+                        ActivationState.NotSupported to
+                            R.string.volume_ringer_hint_mute
+                    mode == AudioManager.RINGER_MODE_SILENT ->
+                        ActivationState.Active to
+                            R.string.volume_ringer_hint_mute
+                    else ->
+                        ActivationState.Inactive to
+                            R.string.volume_ringer_hint_unmute
+                }
+
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    Icon.Resource(
+                        R.drawable.ic_notifications_silence,
+                        ContentDescription.Resource(contentDescriptionRes),
+                    ),
+                    activationState,
+                )
+            }
+            .flowOn(backgroundDispatcher)
+
+    override fun onTriggered(
+        expandable: Expandable?
+    ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        coroutineScope.launch(backgroundDispatcher) {
+            val newRingerMode: Int
+            val currentRingerMode = audioManager.ringerModeInternal
+            if (currentRingerMode == AudioManager.RINGER_MODE_SILENT) {
+                newRingerMode = previousNonSilentMode
+            } else {
+                previousNonSilentMode = currentRingerMode
+                newRingerMode = AudioManager.RINGER_MODE_SILENT
+            }
+
+            if (currentRingerMode != newRingerMode) {
+                audioManager.ringerModeInternal = newRingerMode
+            }
+        }
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+    }
+
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
+        withContext(backgroundDispatcher) {
+            if (audioManager.isVolumeFixed) {
+                KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+            } else {
+                KeyguardQuickAffordanceConfig.PickerScreenState.Default()
+            }
+        }
+
+    /**
+     * Gets the last non-silent ringer mode from shared-preferences if it exists. This is
+     *  cached by [MuteQuickAffordanceCoreStartable] while this affordance is selected
+     */
+    private suspend fun getLastNonSilentRingerMode(): Int =
+        withContext(backgroundDispatcher) {
+            userFileManager.getSharedPreferences(
+                    MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+                    Context.MODE_PRIVATE,
+                    userTracker.userId
+            ).getInt(
+                    LAST_NON_SILENT_RINGER_MODE_KEY,
+                    ringerModeTracker.ringerModeInternal.value ?: DEFAULT_LAST_NON_SILENT_VALUE
+            )
+        }
+
+    private fun <T> LiveData<T>.asFlow(): Flow<T?> =
+        conflatedCallbackFlow {
+            val observer = Observer { value: T -> trySend(value) }
+            observeForever(observer)
+            send(value)
+            awaitClose { removeObserver(observer) }
+        }.flowOn(mainDispatcher)
+
+    companion object {
+        const val LAST_NON_SILENT_RINGER_MODE_KEY = "key_last_non_silent_ringer_mode"
+        const val MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME = "quick_affordance_mute_ringer_mode_cache"
+        private const val DEFAULT_LAST_NON_SILENT_VALUE = AudioManager.RINGER_MODE_NORMAL
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
new file mode 100644
index 0000000..cd0805e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.Observer
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+/**
+ * Store previous non-silent Ringer Mode into shared prefs to be used for Mute Lockscreen Shortcut
+ */
+@SysUISingleton
+class MuteQuickAffordanceCoreStartable @Inject constructor(
+    private val featureFlags: FeatureFlags,
+    private val userTracker: UserTracker,
+    private val ringerModeTracker: RingerModeTracker,
+    private val userFileManager: UserFileManager,
+    private val keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository,
+    @Application private val coroutineScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : CoreStartable {
+
+    private val observer = Observer(this::updateLastNonSilentRingerMode)
+
+    override fun start() {
+        if (!featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)) return
+
+        // only listen to ringerModeInternal changes when Mute is one of the selected affordances
+        keyguardQuickAffordanceRepository
+            .selections
+            .map { selections ->
+                // determines if Mute is selected in any lockscreen shortcut position
+                val muteSelected: Boolean = selections.values.any { configList ->
+                    configList.any { config ->
+                        config.key == BuiltInKeyguardQuickAffordanceKeys.MUTE
+                    }
+                }
+                if (muteSelected) {
+                    ringerModeTracker.ringerModeInternal.observeForever(observer)
+                } else {
+                    ringerModeTracker.ringerModeInternal.removeObserver(observer)
+                }
+            }
+            .launchIn(coroutineScope)
+    }
+
+    private fun updateLastNonSilentRingerMode(lastRingerMode: Int) {
+        coroutineScope.launch(backgroundDispatcher) {
+            if (AudioManager.RINGER_MODE_SILENT != lastRingerMode) {
+                userFileManager.getSharedPreferences(
+                        MuteQuickAffordanceConfig.MUTE_QUICK_AFFORDANCE_PREFS_FILE_NAME,
+                        Context.MODE_PRIVATE,
+                        userTracker.userId
+                )
+                .edit()
+                .putInt(MuteQuickAffordanceConfig.LAST_NON_SILENT_RINGER_MODE_KEY, lastRingerMode)
+                .apply()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 680c06b..4ba2eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -100,9 +100,9 @@
 
     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
         return when {
-            !walletController.isWalletEnabled ->
+            !walletController.walletClient.isWalletServiceAvailable ->
                 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
-            walletController.walletClient.tileIcon == null || queryCards().isEmpty() -> {
+            !walletController.isWalletEnabled || queryCards().isEmpty() -> {
                 val componentName =
                     walletController.walletClient.createWalletSettingsIntent().toComponentName()
                 val actionText =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
new file mode 100644
index 0000000..d9ec3b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -0,0 +1,105 @@
+/*
+ *  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.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraIntents
+import com.android.systemui.camera.CameraIntentsWrapper
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class VideoCameraQuickAffordanceConfig
+@Inject
+constructor(
+    @Application private val context: Context,
+    private val cameraIntents: CameraIntentsWrapper,
+    private val activityIntentHelper: ActivityIntentHelper,
+    private val userTracker: UserTracker,
+) : KeyguardQuickAffordanceConfig {
+
+    private val intent: Intent by lazy {
+        cameraIntents.getVideoCameraIntent().apply {
+            putExtra(
+                CameraIntents.EXTRA_LAUNCH_SOURCE,
+                StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE,
+            )
+        }
+    }
+
+    override val key: String
+        get() = BuiltInKeyguardQuickAffordanceKeys.VIDEO_CAMERA
+
+    override val pickerName: String
+        get() = context.getString(R.string.video_camera)
+
+    override val pickerIconResourceId: Int
+        get() = R.drawable.ic_videocam
+
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+        get() =
+            flowOf(
+                if (isLaunchable()) {
+                    KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                        icon =
+                            Icon.Resource(
+                                R.drawable.ic_videocam,
+                                ContentDescription.Resource(R.string.video_camera)
+                            )
+                    )
+                } else {
+                    KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+                }
+            )
+
+    override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+        return if (isLaunchable()) {
+            super.getPickerScreenState()
+        } else {
+            KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+        }
+    }
+
+    override fun onTriggered(
+        expandable: Expandable?
+    ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+            intent = intent,
+            canShowWhileLocked = false,
+        )
+    }
+
+    private fun isLaunchable(): Boolean {
+        return activityIntentHelper.getTargetActivityInfo(
+            intent,
+            userTracker.userId,
+            true,
+        ) != null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 25d8f40..0af596a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -47,12 +47,13 @@
 import kotlinx.coroutines.flow.transformLatest
 
 /**
- * Acts as source of truth for biometric features.
+ * Acts as source of truth for biometric authentication related settings like enrollments, device
+ * policy, etc.
  *
  * Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
  * upstream changes.
  */
-interface BiometricRepository {
+interface BiometricSettingsRepository {
     /** Whether any fingerprints are enrolled for the current user. */
     val isFingerprintEnrolled: StateFlow<Boolean>
 
@@ -68,7 +69,7 @@
 }
 
 @SysUISingleton
-class BiometricRepositoryImpl
+class BiometricSettingsRepositoryImpl
 @Inject
 constructor(
     context: Context,
@@ -80,7 +81,7 @@
     @Application scope: CoroutineScope,
     @Background backgroundDispatcher: CoroutineDispatcher,
     @Main looper: Looper,
-) : BiometricRepository {
+) : BiometricSettingsRepository {
 
     /** UserId of the current selected user. */
     private val selectedUserId: Flow<Int> =
@@ -88,7 +89,7 @@
 
     override val isFingerprintEnrolled: StateFlow<Boolean> =
         selectedUserId
-            .flatMapLatest { userId ->
+            .flatMapLatest {
                 conflatedCallbackFlow {
                     val callback =
                         object : AuthController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
new file mode 100644
index 0000000..b3a9cf5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import android.hardware.biometrics.BiometricSourceType
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Encapsulates state about device entry fingerprint auth mechanism. */
+interface DeviceEntryFingerprintAuthRepository {
+    /** Whether the device entry fingerprint auth is locked out. */
+    val isLockedOut: StateFlow<Boolean>
+}
+
+/**
+ * Implementation of [DeviceEntryFingerprintAuthRepository] that uses [KeyguardUpdateMonitor] as the
+ * source of truth.
+ *
+ * Dependency on [KeyguardUpdateMonitor] will be removed once fingerprint auth state is moved out of
+ * [KeyguardUpdateMonitor]
+ */
+@SysUISingleton
+class DeviceEntryFingerprintAuthRepositoryImpl
+@Inject
+constructor(
+    val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    @Application scope: CoroutineScope,
+) : DeviceEntryFingerprintAuthRepository {
+
+    override val isLockedOut: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val sendLockoutUpdate =
+                    fun() {
+                        trySendWithFailureLogging(
+                            keyguardUpdateMonitor.isFingerprintLockedOut,
+                            TAG,
+                            "onLockedOutStateChanged"
+                        )
+                    }
+                val callback =
+                    object : KeyguardUpdateMonitorCallback() {
+                        override fun onLockedOutStateChanged(
+                            biometricSourceType: BiometricSourceType?
+                        ) {
+                            if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+                                sendLockoutUpdate()
+                            }
+                        }
+                    }
+                keyguardUpdateMonitor.registerCallback(callback)
+                sendLockoutUpdate()
+                awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+            }
+            .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false)
+
+    companion object {
+        const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 41574d1..4ac6ac8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.log.dagger.BouncerLog
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.phone.KeyguardBouncer
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -42,31 +41,96 @@
  *
  * Make sure to add newly added flows to the logger.
  */
+interface KeyguardBouncerRepository {
+    /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
+    val primaryBouncerVisible: StateFlow<Boolean>
+    val primaryBouncerShow: StateFlow<KeyguardBouncerModel?>
+    val primaryBouncerShowingSoon: StateFlow<Boolean>
+    val primaryBouncerHide: StateFlow<Boolean>
+    val primaryBouncerStartingToHide: StateFlow<Boolean>
+    val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+    /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
+    val primaryBouncerScrimmed: StateFlow<Boolean>
+    /**
+     * Set how much of the notification panel is showing on the screen.
+     * ```
+     *      0f = panel fully hidden = bouncer fully showing
+     *      1f = panel fully showing = bouncer fully hidden
+     * ```
+     */
+    val panelExpansionAmount: StateFlow<Float>
+    val keyguardPosition: StateFlow<Float>
+    val onScreenTurnedOff: StateFlow<Boolean>
+    val isBackButtonEnabled: StateFlow<Boolean?>
+    /** Determines if user is already unlocked */
+    val keyguardAuthenticated: StateFlow<Boolean?>
+    val showMessage: StateFlow<BouncerShowMessageModel?>
+    val resourceUpdateRequests: StateFlow<Boolean>
+    val bouncerPromptReason: Int
+    val bouncerErrorMessage: CharSequence?
+    val isAlternateBouncerVisible: StateFlow<Boolean>
+    val isAlternateBouncerUIAvailable: StateFlow<Boolean>
+    var lastAlternateBouncerVisibleTime: Long
+
+    fun setPrimaryScrimmed(isScrimmed: Boolean)
+
+    fun setPrimaryVisible(isVisible: Boolean)
+
+    fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?)
+
+    fun setPrimaryShowingSoon(showingSoon: Boolean)
+
+    fun setPrimaryHide(hide: Boolean)
+
+    fun setPrimaryStartingToHide(startingToHide: Boolean)
+
+    fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
+
+    fun setPanelExpansion(panelExpansion: Float)
+
+    fun setKeyguardPosition(keyguardPosition: Float)
+
+    fun setResourceUpdateRequests(willUpdateResources: Boolean)
+
+    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
+
+    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
+
+    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
+
+    fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean)
+
+    fun setAlternateVisible(isVisible: Boolean)
+
+    fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
+}
+
 @SysUISingleton
-class KeyguardBouncerRepository
+class KeyguardBouncerRepositoryImpl
 @Inject
 constructor(
     private val viewMediatorCallback: ViewMediatorCallback,
     private val clock: SystemClock,
     @Application private val applicationScope: CoroutineScope,
     @BouncerLog private val buffer: TableLogBuffer,
-) {
+) : KeyguardBouncerRepository {
     /** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
     private val _primaryBouncerVisible = MutableStateFlow(false)
-    val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+    override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
     private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
-    val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
     private val _primaryBouncerShowingSoon = MutableStateFlow(false)
-    val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
     private val _primaryBouncerHide = MutableStateFlow(false)
-    val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+    override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
     private val _primaryBouncerStartingToHide = MutableStateFlow(false)
-    val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
     private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
-    val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
+    override val primaryBouncerStartingDisappearAnimation =
+        _primaryBouncerDisappearAnimation.asStateFlow()
     /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
     private val _primaryBouncerScrimmed = MutableStateFlow(false)
-    val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
     /**
      * Set how much of the notification panel is showing on the screen.
      * ```
@@ -75,23 +139,23 @@
      * ```
      */
     private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
-    val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
     private val _keyguardPosition = MutableStateFlow(0f)
-    val keyguardPosition = _keyguardPosition.asStateFlow()
+    override val keyguardPosition = _keyguardPosition.asStateFlow()
     private val _onScreenTurnedOff = MutableStateFlow(false)
-    val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+    override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
     private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
-    val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
     private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
     /** Determines if user is already unlocked */
-    val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
     private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
-    val showMessage = _showMessage.asStateFlow()
+    override val showMessage = _showMessage.asStateFlow()
     private val _resourceUpdateRequests = MutableStateFlow(false)
-    val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
-    val bouncerPromptReason: Int
+    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    override val bouncerPromptReason: Int
         get() = viewMediatorCallback.bouncerPromptReason
-    val bouncerErrorMessage: CharSequence?
+    override val bouncerErrorMessage: CharSequence?
         get() = viewMediatorCallback.consumeCustomMessage()
 
     init {
@@ -100,21 +164,21 @@
 
     /** Values associated with the AlternateBouncer */
     private val _isAlternateBouncerVisible = MutableStateFlow(false)
-    val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
-    var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
+    override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+    override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
     private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
-    val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
+    override val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
         _isAlternateBouncerUIAvailable.asStateFlow()
 
-    fun setPrimaryScrimmed(isScrimmed: Boolean) {
+    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
         _primaryBouncerScrimmed.value = isScrimmed
     }
 
-    fun setPrimaryVisible(isVisible: Boolean) {
+    override fun setPrimaryVisible(isVisible: Boolean) {
         _primaryBouncerVisible.value = isVisible
     }
 
-    fun setAlternateVisible(isVisible: Boolean) {
+    override fun setAlternateVisible(isVisible: Boolean) {
         if (isVisible && !_isAlternateBouncerVisible.value) {
             lastAlternateBouncerVisibleTime = clock.uptimeMillis()
         } else if (!isVisible) {
@@ -123,55 +187,55 @@
         _isAlternateBouncerVisible.value = isVisible
     }
 
-    fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
         _isAlternateBouncerUIAvailable.value = isAvailable
     }
 
-    fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+    override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
         _primaryBouncerShow.value = keyguardBouncerModel
     }
 
-    fun setPrimaryShowingSoon(showingSoon: Boolean) {
+    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
         _primaryBouncerShowingSoon.value = showingSoon
     }
 
-    fun setPrimaryHide(hide: Boolean) {
+    override fun setPrimaryHide(hide: Boolean) {
         _primaryBouncerHide.value = hide
     }
 
-    fun setPrimaryStartingToHide(startingToHide: Boolean) {
+    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
         _primaryBouncerStartingToHide.value = startingToHide
     }
 
-    fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
         _primaryBouncerDisappearAnimation.value = runnable
     }
 
-    fun setPanelExpansion(panelExpansion: Float) {
+    override fun setPanelExpansion(panelExpansion: Float) {
         _panelExpansionAmount.value = panelExpansion
     }
 
-    fun setKeyguardPosition(keyguardPosition: Float) {
+    override fun setKeyguardPosition(keyguardPosition: Float) {
         _keyguardPosition.value = keyguardPosition
     }
 
-    fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
         _resourceUpdateRequests.value = willUpdateResources
     }
 
-    fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
         _showMessage.value = bouncerShowMessageModel
     }
 
-    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
         _keyguardAuthenticated.value = keyguardAuthenticated
     }
 
-    fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
         _isBackButtonEnabled.value = isBackButtonEnabled
     }
 
-    fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+    override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
         _onScreenTurnedOff.value = onScreenTurnedOff
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 2b2b9d0..8ece318 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -146,7 +146,7 @@
      * Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
      * slot with the given ID. The configs are sorted in descending priority order.
      */
-    fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+    fun getCurrentSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
         val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList())
         return configs.filter { selections.contains(it.key) }
     }
@@ -155,7 +155,7 @@
      * Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
      * are sorted in descending priority order.
      */
-    fun getSelections(): Map<String, List<String>> {
+    fun getCurrentSelections(): Map<String, List<String>> {
         return selectionManager.value.getSelections()
     }
 
@@ -217,7 +217,7 @@
     private inner class Dumpster : Dumpable {
         override fun dump(pw: PrintWriter, args: Array<out String>) {
             val slotPickerRepresentations = getSlotPickerRepresentations()
-            val selectionsBySlotId = getSelections()
+            val selectionsBySlotId = getCurrentSelections()
             pw.println("Slots & selections:")
             slotPickerRepresentations.forEach { slotPickerRepresentation ->
                 val slotId = slotPickerRepresentation.id
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index d99af90..db95562 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -148,6 +148,9 @@
     /** Source of the most recent biometric unlock, such as fingerprint or face. */
     val biometricUnlockSource: Flow<BiometricUnlockSource?>
 
+    /** Whether quick settings or quick-quick settings is visible. */
+    val isQuickSettingsVisible: Flow<Boolean>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -172,6 +175,9 @@
      * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
      */
     fun isUdfpsSupported(): Boolean
+
+    /** Sets whether quick settings or quick-quick settings is visible. */
+    fun setQuickSettingsVisible(isVisible: Boolean)
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -581,6 +587,9 @@
         awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
     }
 
+    private val _isQuickSettingsVisible = MutableStateFlow(false)
+    override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
@@ -595,6 +604,10 @@
 
     override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
 
+    override fun setQuickSettingsVisible(isVisible: Boolean) {
+        _isQuickSettingsVisible.value = isVisible
+    }
+
     private fun statusBarStateIntToObject(value: Int): StatusBarState {
         return when (value) {
             0 -> StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 4639597..4a262f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -31,5 +31,16 @@
     @Binds
     fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
 
-    @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository
+    @Binds
+    fun biometricSettingsRepository(
+        impl: BiometricSettingsRepositoryImpl
+    ): BiometricSettingsRepository
+
+    @Binds
+    fun deviceEntryFingerprintAuthRepository(
+        impl: DeviceEntryFingerprintAuthRepositoryImpl
+    ): DeviceEntryFingerprintAuthRepository
+
+    @Binds
+    fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
new file mode 100644
index 0000000..d90f328
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import com.android.keyguard.logging.TrustRepositoryLogger
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+interface TrustRepository {
+    /** Flow representing whether the current user is trusted. */
+    val isCurrentUserTrusted: Flow<Boolean>
+}
+
+@SysUISingleton
+class TrustRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val userRepository: UserRepository,
+    private val trustManager: TrustManager,
+    private val logger: TrustRepositoryLogger,
+) : TrustRepository {
+    private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>()
+
+    private val trust =
+        conflatedCallbackFlow {
+                val callback =
+                    object : TrustManager.TrustListener {
+                        override fun onTrustChanged(
+                            enabled: Boolean,
+                            newlyUnlocked: Boolean,
+                            userId: Int,
+                            flags: Int,
+                            grantMsgs: List<String>?
+                        ) {
+                            logger.onTrustChanged(enabled, newlyUnlocked, userId, flags, grantMsgs)
+                            trySendWithFailureLogging(
+                                TrustModel(enabled, userId),
+                                TrustRepositoryLogger.TAG,
+                                "onTrustChanged"
+                            )
+                        }
+
+                        override fun onTrustError(message: CharSequence?) = Unit
+
+                        override fun onTrustManagedChanged(enabled: Boolean, userId: Int) = Unit
+                    }
+                trustManager.registerTrustListener(callback)
+                logger.trustListenerRegistered()
+                awaitClose {
+                    logger.trustListenerUnregistered()
+                    trustManager.unregisterTrustListener(callback)
+                }
+            }
+            .onEach {
+                latestTrustModelForUser[it.userId] = it
+                logger.trustModelEmitted(it)
+            }
+            .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
+
+    override val isCurrentUserTrusted: Flow<Boolean>
+        get() =
+            combine(trust, userRepository.selectedUserInfo, ::Pair)
+                .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
+                .distinctUntilChanged()
+                .onEach { logger.isCurrentUserTrusted(it) }
+                .onStart { emit(false) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index 28c0b28..6452e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -20,7 +20,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
 import com.android.systemui.util.time.SystemClock
@@ -33,7 +34,8 @@
 @Inject
 constructor(
     private val bouncerRepository: KeyguardBouncerRepository,
-    private val biometricRepository: BiometricRepository,
+    private val biometricSettingsRepository: BiometricSettingsRepository,
+    private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
     private val systemClock: SystemClock,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     featureFlags: FeatureFlags,
@@ -97,9 +99,10 @@
     fun canShowAlternateBouncerForFingerprint(): Boolean {
         return if (isModernAlternateBouncerEnabled) {
             bouncerRepository.isAlternateBouncerUIAvailable.value &&
-                biometricRepository.isFingerprintEnrolled.value &&
-                biometricRepository.isStrongBiometricAllowed.value &&
-                biometricRepository.isFingerprintEnabledByDevicePolicy.value
+                biometricSettingsRepository.isFingerprintEnrolled.value &&
+                biometricSettingsRepository.isStrongBiometricAllowed.value &&
+                biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
+                !deviceEntryFingerprintAuthRepository.isLockedOut.value
         } else {
             legacyAlternateBouncer != null &&
                 keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index ce61f2f..86f65dde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
@@ -44,6 +45,7 @@
 
     override fun start() {
         listenForDozingToLockscreen()
+        listenForDozingToGone()
     }
 
     private fun listenForDozingToLockscreen() {
@@ -68,6 +70,28 @@
         }
     }
 
+    private fun listenForDozingToGone() {
+        scope.launch {
+            keyguardInteractor.biometricUnlockState
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (biometricUnlockState, lastStartedTransition) ->
+                    if (
+                        lastStartedTransition.to == KeyguardState.DOZING &&
+                            isWakeAndUnlock(biometricUnlockState)
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.DOZING,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
     private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
         return ValueAnimator().apply {
             setInterpolator(Interpolators.LINEAR)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7134ec0..81a5828 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -31,8 +31,10 @@
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -87,6 +89,9 @@
     private fun listenForDreamingToOccluded() {
         scope.launch {
             keyguardInteractor.isDreaming
+                // Add a slight delay, as dreaming and occluded events will arrive with a small gap
+                // in time. This prevents a transition to OCCLUSION happening prematurely.
+                .onEach { delay(50) }
                 .sample(
                     combine(
                         keyguardInteractor.isKeyguardOccluded,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 9203a9b..b5bcd45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -45,15 +45,35 @@
     override fun start() {
         listenForGoneToAodOrDozing()
         listenForGoneToDreaming()
+        listenForGoneToLockscreen()
+    }
+
+    // Primarily for when the user chooses to lock down the device
+    private fun listenForGoneToLockscreen() {
+        scope.launch {
+            keyguardInteractor.isKeyguardShowing
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (isKeyguardShowing, lastStartedStep) ->
+                    if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.GONE,
+                                KeyguardState.LOCKSCREEN,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
     }
 
     private fun listenForGoneToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
-                .collect { pair ->
-                    val (isAbleToDream, keyguardState) = pair
-                    if (isAbleToDream && keyguardState == KeyguardState.GONE) {
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (isAbleToDream, lastStartedStep) ->
+                    if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
@@ -72,15 +92,15 @@
             keyguardInteractor.wakefulnessModel
                 .sample(
                     combine(
-                        keyguardTransitionInteractor.finishedKeyguardState,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
                         ::Pair
                     ),
                     ::toTriple
                 )
-                .collect { (wakefulnessState, keyguardState, isAodAvailable) ->
+                .collect { (wakefulnessState, lastStartedStep, isAodAvailable) ->
                     if (
-                        keyguardState == KeyguardState.GONE &&
+                        lastStartedStep.to == KeyguardState.GONE &&
                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
                     ) {
                         keyguardTransitionRepository.startTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 4cf56fe..3d39da6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -155,6 +155,11 @@
         }
     }
 
+    /** Sets whether quick settings or quick-quick settings is visible. */
+    fun setQuickSettingsVisible(isVisible: Boolean) {
+        repository.setQuickSettingsVisible(isVisible)
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
new file mode 100644
index 0000000..6525a13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
@@ -0,0 +1,187 @@
+/*
+ * 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.systemui.keyguard.domain.interactor
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.shared.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.domain.model.KeyguardSettingsPopupMenuModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Business logic for use-cases related to the keyguard long-press feature. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardLongPressInteractor
+@Inject
+constructor(
+    @Application unsafeContext: Context,
+    @Application scope: CoroutineScope,
+    transitionInteractor: KeyguardTransitionInteractor,
+    repository: KeyguardRepository,
+    private val activityStarter: ActivityStarter,
+    private val logger: UiEventLogger,
+    private val featureFlags: FeatureFlags,
+    broadcastDispatcher: BroadcastDispatcher,
+) {
+    private val appContext = unsafeContext.applicationContext
+
+    private val _isLongPressHandlingEnabled: StateFlow<Boolean> =
+        if (isFeatureEnabled()) {
+                combine(
+                    transitionInteractor.finishedKeyguardState.map {
+                        it == KeyguardState.LOCKSCREEN
+                    },
+                    repository.isQuickSettingsVisible,
+                ) { isFullyTransitionedToLockScreen, isQuickSettingsVisible ->
+                    isFullyTransitionedToLockScreen && !isQuickSettingsVisible
+                }
+            } else {
+                flowOf(false)
+            }
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /** Whether the long-press handling feature should be enabled. */
+    val isLongPressHandlingEnabled: Flow<Boolean> = _isLongPressHandlingEnabled
+
+    private val _menu = MutableStateFlow<KeyguardSettingsPopupMenuModel?>(null)
+    /** Model for a menu that should be shown; `null` when no menu should be shown. */
+    val menu: Flow<KeyguardSettingsPopupMenuModel?> =
+        isLongPressHandlingEnabled.flatMapLatest { isEnabled ->
+            if (isEnabled) {
+                _menu
+            } else {
+                flowOf(null)
+            }
+        }
+
+    init {
+        if (isFeatureEnabled()) {
+            broadcastDispatcher
+                .broadcastFlow(
+                    IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+                )
+                .onEach { hideMenu() }
+                .launchIn(scope)
+        }
+    }
+
+    /** Notifies that the user has long-pressed on the lock screen. */
+    fun onLongPress(x: Int, y: Int) {
+        if (!_isLongPressHandlingEnabled.value) {
+            return
+        }
+
+        showMenu(
+            x = x,
+            y = y,
+        )
+    }
+
+    private fun isFeatureEnabled(): Boolean {
+        return featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED) &&
+            featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI)
+    }
+
+    /** Updates application state to ask to show the menu at the given coordinates. */
+    private fun showMenu(
+        x: Int,
+        y: Int,
+    ) {
+        _menu.value =
+            KeyguardSettingsPopupMenuModel(
+                position =
+                    Position(
+                        x = x,
+                        y = y,
+                    ),
+                onClicked = {
+                    hideMenu()
+                    navigateToLockScreenSettings()
+                },
+                onDismissed = { hideMenu() },
+            )
+        logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
+    }
+
+    /** Updates application state to ask to hide the menu. */
+    private fun hideMenu() {
+        _menu.value = null
+    }
+
+    /** Opens the wallpaper picker screen after the device is unlocked by the user. */
+    private fun navigateToLockScreenSettings() {
+        logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
+        activityStarter.dismissKeyguardThenExecute(
+            /* action= */ {
+                appContext.startActivity(
+                    Intent(Intent.ACTION_SET_WALLPAPER).apply {
+                        flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                        appContext
+                            .getString(R.string.config_wallpaperPickerPackage)
+                            .takeIf { it.isNotEmpty() }
+                            ?.let { packageName -> setPackage(packageName) }
+                    }
+                )
+                true
+            },
+            /* cancel= */ {},
+            /* afterKeyguardGone= */ true,
+        )
+    }
+
+    enum class LogEvents(
+        private val _id: Int,
+    ) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "The lock screen was long-pressed and we showed the settings popup menu.")
+        LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN(1292),
+        @UiEvent(doc = "The lock screen long-press popup menu was clicked.")
+        LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED(1293),
+        ;
+
+        override fun getId() = _id
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index c219380..b2da793 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -154,7 +154,7 @@
         val slots = repository.get().getSlotPickerRepresentations()
         val slot = slots.find { it.id == slotId } ?: return false
         val selections =
-            repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+            repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
         val alreadySelected = selections.remove(affordanceId)
         if (!alreadySelected) {
             while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
@@ -193,7 +193,7 @@
 
         if (affordanceId.isNullOrEmpty()) {
             return if (
-                repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty()
+                repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).isEmpty()
             ) {
                 false
             } else {
@@ -203,7 +203,7 @@
         }
 
         val selections =
-            repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+            repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList()
         return if (selections.remove(affordanceId)) {
             repository
                 .get()
@@ -220,7 +220,7 @@
     /** Returns affordance IDs indexed by slot ID, for all known slots. */
     suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
         val slots = repository.get().getSlotPickerRepresentations()
-        val selections = repository.get().getSelections()
+        val selections = repository.get().getCurrentSelections()
         val affordanceById =
             getAffordancePickerRepresentations().associateBy { affordance -> affordance.id }
         return slots.associate { slot ->
@@ -363,6 +363,14 @@
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED,
                 value = featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
             ),
+            KeyguardPickerFlag(
+                name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW,
+                value = featureFlags.isEnabled(Flags.WALLPAPER_FULLSCREEN_PREVIEW),
+            ),
+            KeyguardPickerFlag(
+                name = Contract.FlagsTable.FLAG_NAME_MONOCHROMATIC_THEME,
+                value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME)
+            )
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index ad6dbea..53c80f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,7 +19,6 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
@@ -31,9 +30,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import javax.inject.Inject
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.time.Duration
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -104,38 +100,4 @@
     /* The last completed [KeyguardState] transition */
     val finishedKeyguardState: Flow<KeyguardState> =
         finishedKeyguardTransitionStep.map { step -> step.to }
-
-    /**
-     * Transitions will occur over a [totalDuration] with [TransitionStep]s being emitted in the
-     * range of [0, 1]. View animations should begin and end within a subset of this range. This
-     * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
-     */
-    fun transitionStepAnimation(
-        flow: Flow<TransitionStep>,
-        params: AnimationParams,
-        totalDuration: Duration,
-    ): Flow<Float> {
-        val start = (params.startTime / totalDuration).toFloat()
-        val chunks = (totalDuration / params.duration).toFloat()
-        var isRunning = false
-        return flow
-            .map { step ->
-                val value = (step.value - start) * chunks
-                if (step.transitionState == STARTED) {
-                    // When starting, make sure to always emit. If a transition is started from the
-                    // middle, it is possible this animation is being skipped but we need to inform
-                    // the ViewModels of the last update
-                    isRunning = true
-                    max(0f, min(1f, value))
-                } else if (isRunning && value >= 1f) {
-                    // Always send a final value of 1. Because of rounding, [value] may never be
-                    // exactly 1.
-                    isRunning = false
-                    1f
-                } else {
-                    value
-                }
-            }
-            .filter { value -> value >= 0f && value <= 1f }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
index c5e49c6..3099a49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractor.kt
@@ -18,27 +18,29 @@
 
 import android.view.View
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
 import com.android.systemui.util.ListenerSet
 import javax.inject.Inject
 
 /** Interactor to add and remove callbacks for the bouncer. */
 @SysUISingleton
 class PrimaryBouncerCallbackInteractor @Inject constructor() {
-    private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>()
-    private var expansionCallbacks = ArrayList<KeyguardBouncer.PrimaryBouncerExpansionCallback>()
+    private var resetCallbacks = ListenerSet<KeyguardResetCallback>()
+    private var expansionCallbacks = ArrayList<PrimaryBouncerExpansionCallback>()
+
     /** Add a KeyguardResetCallback. */
-    fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
+    fun addKeyguardResetCallback(callback: KeyguardResetCallback) {
         resetCallbacks.addIfAbsent(callback)
     }
 
     /** Remove a KeyguardResetCallback. */
-    fun removeKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
+    fun removeKeyguardResetCallback(callback: KeyguardResetCallback) {
         resetCallbacks.remove(callback)
     }
 
     /** Adds a callback to listen to bouncer expansion updates. */
-    fun addBouncerExpansionCallback(callback: KeyguardBouncer.PrimaryBouncerExpansionCallback) {
+    fun addBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
         if (!expansionCallbacks.contains(callback)) {
             expansionCallbacks.add(callback)
         }
@@ -48,7 +50,7 @@
      * Removes a previously added callback. If the callback was never added, this method does
      * nothing.
      */
-    fun removeBouncerExpansionCallback(callback: KeyguardBouncer.PrimaryBouncerExpansionCallback) {
+    fun removeBouncerExpansionCallback(callback: PrimaryBouncerExpansionCallback) {
         expansionCallbacks.remove(callback)
     }
 
@@ -99,4 +101,40 @@
             callback.onKeyguardReset()
         }
     }
+
+    /** Callback updated when the primary bouncer's show and hide states change. */
+    interface PrimaryBouncerExpansionCallback {
+        /**
+         * Invoked when the bouncer expansion reaches [EXPANSION_VISIBLE]. This is NOT called each
+         * time the bouncer is shown, but rather only when the fully shown amount has changed based
+         * on the panel expansion. The bouncer's visibility can still change when the expansion
+         * amount hasn't changed. See [PrimaryBouncerInteractor.isFullyShowing] for the checks for
+         * the bouncer showing state.
+         */
+        fun onFullyShown() {}
+
+        /** Invoked when the bouncer is starting to transition to a hidden state. */
+        fun onStartingToHide() {}
+
+        /** Invoked when the bouncer is starting to transition to a visible state. */
+        fun onStartingToShow() {}
+
+        /** Invoked when the bouncer expansion reaches [EXPANSION_HIDDEN]. */
+        fun onFullyHidden() {}
+
+        /**
+         * From 0f [EXPANSION_VISIBLE] when fully visible to 1f [EXPANSION_HIDDEN] when fully hidden
+         */
+        fun onExpansionChanged(bouncerHideAmount: Float) {}
+
+        /**
+         * Invoked when visibility of KeyguardBouncer has changed. Note the bouncer expansion can be
+         * [EXPANSION_VISIBLE], but the view's visibility can be [View.INVISIBLE].
+         */
+        fun onVisibilityChanged(isVisible: Boolean) {}
+    }
+
+    interface KeyguardResetCallback {
+        fun onKeyguardReset()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index a92540d..96bf815 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -113,6 +113,8 @@
                 0f
             }
         }
+    /** Allow for interaction when just about fully visible */
+    val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
 
     // TODO(b/243685699): Move isScrimmed logic to data layer.
     // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt
new file mode 100644
index 0000000..7c61e71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.systemui.keyguard.domain.model
+
+import com.android.systemui.common.shared.model.Position
+
+/** Models a settings popup menu for the lock screen. */
+data class KeyguardSettingsPopupMenuModel(
+    /** Where the menu should be anchored, roughly in screen space. */
+    val position: Position,
+    /** Callback to invoke when the menu gets clicked by the user. */
+    val onClicked: () -> Unit,
+    /** Callback to invoke when the menu gets dismissed by the user. */
+    val onDismissed: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
index bb5ac84..8222dd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt
@@ -25,4 +25,5 @@
      */
     const val EXPANSION_HIDDEN = 1f
     const val EXPANSION_VISIBLE = 0f
+    const val ALPHA_EXPANSION_THRESHOLD = 0.95f
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
index 67733e9..4fd14b1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -11,15 +11,15 @@
  * 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
+ * limitations under the License.
  */
+
 package com.android.systemui.keyguard.shared.model
 
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
-
-/** Animation parameters */
-data class AnimationParams(
-    val startTime: Duration = 0.milliseconds,
-    val duration: Duration,
+/** Represents the trust state */
+data class TrustModel(
+    /** If true, the system believes the environment to be trusted. */
+    val isTrusted: Boolean,
+    /** The user, for which the trust changed. */
+    val userId: Int,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
new file mode 100644
index 0000000..ca1e27c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.systemui.keyguard.ui
+
+import android.view.animation.Interpolator
+import com.android.systemui.animation.Interpolators.LINEAR
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/**
+ * For the given transition params, construct a flow using [createFlow] for the specified portion of
+ * the overall transition.
+ */
+class KeyguardTransitionAnimationFlow(
+    private val transitionDuration: Duration,
+    private val transitionFlow: Flow<TransitionStep>,
+) {
+    /**
+     * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted in
+     * the range of [0, 1]. View animations should begin and end within a subset of this range. This
+     * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
+     */
+    fun createFlow(
+        duration: Duration,
+        onStep: (Float) -> Float,
+        startTime: Duration = 0.milliseconds,
+        onCancel: (() -> Float)? = null,
+        onFinish: (() -> Float)? = null,
+        interpolator: Interpolator = LINEAR,
+    ): Flow<Float> {
+        if (!duration.isPositive()) {
+            throw IllegalArgumentException("duration must be a positive number: $duration")
+        }
+        if ((startTime + duration).compareTo(transitionDuration) > 0) {
+            throw IllegalArgumentException(
+                "startTime($startTime) + duration($duration) must be" +
+                    " <= transitionDuration($transitionDuration)"
+            )
+        }
+
+        val start = (startTime / transitionDuration).toFloat()
+        val chunks = (transitionDuration / duration).toFloat()
+        var isComplete = true
+
+        fun stepToValue(step: TransitionStep): Float? {
+            val value = (step.value - start) * chunks
+            return when (step.transitionState) {
+                // When starting, make sure to always emit. If a transition is started from the
+                // middle, it is possible this animation is being skipped but we need to inform
+                // the ViewModels of the last update
+                STARTED -> {
+                    isComplete = false
+                    max(0f, min(1f, value))
+                }
+                // Always send a final value of 1. Because of rounding, [value] may never be
+                // exactly 1.
+                RUNNING ->
+                    if (isComplete) {
+                        null
+                    } else if (value >= 1f) {
+                        isComplete = true
+                        1f
+                    } else if (value >= 0f) {
+                        value
+                    } else {
+                        null
+                    }
+                else -> null
+            }?.let { onStep(interpolator.getInterpolation(it)) }
+        }
+
+        return transitionFlow
+            .map { step ->
+                when (step.transitionState) {
+                    STARTED -> stepToValue(step)
+                    RUNNING -> stepToValue(step)
+                    CANCELED -> onCancel?.invoke()
+                    FINISHED -> onFinish?.invoke()
+                }
+            }
+            .filterNotNull()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 9d8bf7d..3319f9d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -49,6 +49,7 @@
 import kotlin.math.sqrt
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
@@ -66,6 +67,8 @@
 object KeyguardBottomAreaViewBinder {
 
     private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
+    private const val SCALE_SELECTED_BUTTON = 1.23f
+    private const val DIM_ALPHA = 0.3f
 
     /**
      * Defines interface for an object that acts as the binding between the view and its view-model.
@@ -161,12 +164,26 @@
 
                         ambientIndicationArea?.alpha = alpha
                         indicationArea.alpha = alpha
-                        startButton.alpha = alpha
-                        endButton.alpha = alpha
                     }
                 }
 
                 launch {
+                    updateButtonAlpha(
+                        view = startButton,
+                        viewModel = viewModel.startButton,
+                        alphaFlow = viewModel.alpha,
+                    )
+                }
+
+                launch {
+                    updateButtonAlpha(
+                        view = endButton,
+                        viewModel = viewModel.endButton,
+                        alphaFlow = viewModel.alpha,
+                    )
+                }
+
+                launch {
                     viewModel.indicationAreaTranslationX.collect { translationX ->
                         indicationArea.translationX = translationX
                         ambientIndicationArea?.translationX = translationX
@@ -315,6 +332,11 @@
             } else {
                 null
             }
+        view
+            .animate()
+            .scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
+            .scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
+            .start()
 
         view.isClickable = viewModel.isClickable
         if (viewModel.isClickable) {
@@ -333,6 +355,17 @@
         view.isSelected = viewModel.isSelected
     }
 
+    private suspend fun updateButtonAlpha(
+        view: View,
+        viewModel: Flow<KeyguardQuickAffordanceViewModel>,
+        alphaFlow: Flow<Float>,
+    ) {
+        combine(viewModel.map { it.isDimmed }, alphaFlow) { isDimmed, alpha ->
+                if (isDimmed) DIM_ALPHA else alpha
+            }
+            .collect { view.alpha = it }
+    }
+
     private class OnTouchListener(
         private val view: View,
         private val viewModel: KeyguardQuickAffordanceViewModel,
@@ -342,14 +375,12 @@
 
         private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
         private var longPressAnimator: ViewPropertyAnimator? = null
-        private var downTimestamp = 0L
 
         @SuppressLint("ClickableViewAccessibility")
         override fun onTouch(v: View?, event: MotionEvent?): Boolean {
             return when (event?.actionMasked) {
                 MotionEvent.ACTION_DOWN ->
                     if (viewModel.configKey != null) {
-                        downTimestamp = System.currentTimeMillis()
                         longPressAnimator =
                             view
                                 .animate()
@@ -396,7 +427,7 @@
                 MotionEvent.ACTION_UP -> {
                     cancel(
                         onAnimationEnd =
-                            if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
+                            if (event.eventTime - event.downTime < longPressDurationMs) {
                                 Runnable {
                                     messageDisplayer.invoke(
                                         R.string.keyguard_affordance_press_too_short
@@ -437,7 +468,6 @@
         }
 
         private fun cancel(onAnimationEnd: Runnable? = null) {
-            downTimestamp = 0L
             longPressAnimator?.cancel()
             longPressAnimator = null
             view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 49d3748..9f09d53 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -19,6 +19,7 @@
 import android.view.KeyEvent
 import android.view.View
 import android.view.ViewGroup
+import android.window.OnBackAnimationCallback
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.internal.policy.SystemBarUtils
@@ -55,6 +56,10 @@
                         mode == KeyguardSecurityModel.SecurityMode.SimPuk
                 }
 
+                override fun getBackCallback(): OnBackAnimationCallback {
+                    return hostViewController.backCallback
+                }
+
                 override fun shouldDismissOnMenuPressed(): Boolean {
                     return hostViewController.shouldEnableMenuKey()
                 }
@@ -101,13 +106,6 @@
                             hostViewController.appear(
                                 SystemBarUtils.getStatusBarHeight(view.context)
                             )
-                        }
-                    }
-
-                    launch {
-                        viewModel.showWithFullExpansion.collect { model ->
-                            hostViewController.resetSecurityContainer()
-                            hostViewController.showPromptReason(model.promptReason)
                             hostViewController.onResume()
                         }
                     }
@@ -156,6 +154,12 @@
                     }
 
                     launch {
+                        viewModel.isInteractable.collect { isInteractable ->
+                            hostViewController.setInteractable(isInteractable)
+                        }
+                    }
+
+                    launch {
                         viewModel.isBouncerVisible
                             .filter { !it }
                             .collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt
new file mode 100644
index 0000000..d85682b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.systemui.keyguard.ui.binder
+
+import android.annotation.SuppressLint
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.PopupWindow
+import com.android.systemui.R
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsPopupMenuViewModel
+
+object KeyguardLongPressPopupViewBinder {
+    @SuppressLint("InflateParams") // We don't care that the parent is null.
+    fun createAndShow(
+        container: View,
+        viewModel: KeyguardSettingsPopupMenuViewModel,
+        onDismissed: () -> Unit,
+    ): () -> Unit {
+        val contentView: View =
+            LayoutInflater.from(container.context)
+                .inflate(
+                    R.layout.keyguard_settings_popup_menu,
+                    null,
+                )
+
+        contentView.setOnClickListener { viewModel.onClicked() }
+        IconViewBinder.bind(
+            icon = viewModel.icon,
+            view = contentView.requireViewById(R.id.icon),
+        )
+        TextViewBinder.bind(
+            view = contentView.requireViewById(R.id.text),
+            viewModel = viewModel.text,
+        )
+
+        val popupWindow =
+            PopupWindow(container.context).apply {
+                windowLayoutType = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
+                setBackgroundDrawable(null)
+                animationStyle = com.android.internal.R.style.Animation_Dialog
+                isOutsideTouchable = true
+                isFocusable = true
+                setContentView(contentView)
+                setOnDismissListener { onDismissed() }
+                contentView.measure(
+                    View.MeasureSpec.makeMeasureSpec(
+                        0,
+                        View.MeasureSpec.UNSPECIFIED,
+                    ),
+                    View.MeasureSpec.makeMeasureSpec(
+                        0,
+                        View.MeasureSpec.UNSPECIFIED,
+                    ),
+                )
+                showAtLocation(
+                    container,
+                    Gravity.NO_GRAVITY,
+                    viewModel.position.x - contentView.measuredWidth / 2,
+                    viewModel.position.y -
+                        contentView.measuredHeight -
+                        container.context.resources.getDimensionPixelSize(
+                            R.dimen.keyguard_long_press_settings_popup_vertical_offset
+                        ),
+                )
+            }
+
+        return { popupWindow.dismiss() }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
new file mode 100644
index 0000000..ef3f242
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.systemui.keyguard.ui.binder
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import kotlinx.coroutines.launch
+
+object KeyguardLongPressViewBinder {
+    /**
+     * Drives UI for the lock screen long-press feature.
+     *
+     * @param view The view that listens for long-presses.
+     * @param viewModel The view-model that models the UI state.
+     * @param onSingleTap A callback to invoke when the system decides that there was a single tap.
+     * @param falsingManager [FalsingManager] for making sure the long-press didn't just happen in
+     * the user's pocket.
+     */
+    @JvmStatic
+    fun bind(
+        view: LongPressHandlingView,
+        viewModel: KeyguardLongPressViewModel,
+        onSingleTap: () -> Unit,
+        falsingManager: FalsingManager,
+    ) {
+        view.listener =
+            object : LongPressHandlingView.Listener {
+                override fun onLongPressDetected(view: View, x: Int, y: Int) {
+                    if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) {
+                        return
+                    }
+
+                    viewModel.onLongPress(
+                        x = x,
+                        y = y,
+                    )
+                }
+
+                override fun onSingleTapDetected(view: View) {
+                    if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                        return
+                    }
+
+                    onSingleTap()
+                }
+            }
+
+        view.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    viewModel.isLongPressHandlingEnabled.collect { isEnabled ->
+                        view.setLongPressHandlingEnabled(isEnabled)
+                    }
+                }
+
+                launch {
+                    var dismissMenu: (() -> Unit)? = null
+
+                    viewModel.menu.collect { menuOrNull ->
+                        if (menuOrNull != null) {
+                            dismissMenu =
+                                KeyguardLongPressPopupViewBinder.createAndShow(
+                                    container = view,
+                                    viewModel = menuOrNull,
+                                    onDismissed = menuOrNull.onDismissed,
+                                )
+                        } else {
+                            dismissMenu?.invoke()
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index a5ae8ba5..adde595 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -24,7 +24,6 @@
 import android.hardware.display.DisplayManager
 import android.os.Bundle
 import android.os.IBinder
-import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.SurfaceControlViewHost
 import android.view.View
@@ -65,6 +64,11 @@
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
     private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
     private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
+    private val shouldHighlightSelectedAffordance: Boolean =
+        bundle.getBoolean(
+            KeyguardQuickAffordancePreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
+            false,
+        )
 
     private var host: SurfaceControlViewHost
 
@@ -82,6 +86,7 @@
                 bundle.getString(
                     KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
                 ),
+            shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
         )
         runBlocking(mainDispatcher) {
             host =
@@ -154,8 +159,7 @@
             bottomAreaView,
             FrameLayout.LayoutParams(
                 FrameLayout.LayoutParams.MATCH_PARENT,
-                FrameLayout.LayoutParams.WRAP_CONTENT,
-                Gravity.BOTTOM,
+                FrameLayout.LayoutParams.MATCH_PARENT,
             ),
         )
     }
@@ -173,7 +177,8 @@
         val receiver =
             object : BroadcastReceiver() {
                 override fun onReceive(context: Context?, intent: Intent?) {
-                    clockController.clock?.events?.onTimeTick()
+                    clockController.clock?.smallClock?.events?.onTimeTick()
+                    clockController.clock?.largeClock?.events?.onTimeTick()
                 }
             }
         broadcastDispatcher.registerReceiver(
@@ -195,7 +200,13 @@
             ?.events
             ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
         clockView?.let { parentView.removeView(it) }
-        clockView = clockController.clock?.largeClock?.view?.apply { parentView.addView(this) }
+        clockView =
+            clockController.clock?.largeClock?.view?.apply {
+                if (shouldHighlightSelectedAffordance) {
+                    alpha = DIM_ALPHA
+                }
+                parentView.addView(this)
+            }
     }
 
     companion object {
@@ -203,5 +214,7 @@
         private const val KEY_VIEW_WIDTH = "width"
         private const val KEY_VIEW_HEIGHT = "height"
         private const val KEY_DISPLAY_ID = "display_id"
+
+        private const val DIM_ALPHA = 0.3f
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index 6627865..8d6545a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -21,15 +21,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 
 /**
  * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -41,39 +36,46 @@
 constructor(
     private val interactor: KeyguardTransitionInteractor,
 ) {
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_LOCKSCREEN_DURATION,
+            transitionFlow = interactor.dreamingToLockscreenTransition,
+        )
 
     /** Dream overlay y-translation on exit */
     fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
-        return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value ->
-            EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx
-        }
+        return transitionAnimation.createFlow(
+            duration = 600.milliseconds,
+            onStep = { it * translatePx },
+            interpolator = EMPHASIZED_ACCELERATE,
+        )
     }
     /** Dream overlay views alpha - fade out */
-    val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
+    val dreamOverlayAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 250.milliseconds,
+            onStep = { 1f - it },
+        )
 
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return merge(
-            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-                -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
-            },
-            // On end, reset the translation to 0
-            interactor.dreamingToLockscreenTransition
-                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
-                .map { 0f }
+        return transitionAnimation.createFlow(
+            duration = TO_LOCKSCREEN_DURATION,
+            onStep = { value -> -translatePx + value * translatePx },
+            // Reset on cancel or finish
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_DECELERATE,
         )
     }
 
     /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.dreamingToLockscreenTransition,
-            params,
-            totalDuration = TO_LOCKSCREEN_DURATION
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            startTime = 233.milliseconds,
+            duration = 250.milliseconds,
+            onStep = { it },
         )
-    }
 
     companion object {
         /* Length of time before ending the dream activity, in order to start unoccluding */
@@ -81,11 +83,5 @@
         @JvmField
         val LOCKSCREEN_ANIMATION_DURATION_MS =
             (TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds
-
-        val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds)
-        val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
-        val LOCKSCREEN_ALPHA =
-            AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
index 5a47960..f16827d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 
 /** Breaks down GONE->DREAMING transition into discrete steps for corresponding views to consume. */
 @SysUISingleton
@@ -38,32 +33,28 @@
     private val interactor: KeyguardTransitionInteractor,
 ) {
 
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_DREAMING_DURATION,
+            transitionFlow = interactor.goneToDreamingTransition,
+        )
+
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return merge(
-            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-                (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
-            },
-            // On end, reset the translation to 0
-            interactor.goneToDreamingTransition
-                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
-                .map { 0f }
+        return transitionAnimation.createFlow(
+            duration = 500.milliseconds,
+            onStep = { it * translatePx },
+            // Reset on cancel or finish
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_ACCELERATE,
         )
     }
 
     /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.goneToDreamingTransition,
-            params,
-            totalDuration = TO_DREAMING_DURATION
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 250.milliseconds,
+            onStep = { 1f - it },
         )
-    }
-
-    companion object {
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
-        val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 5d85680..1e3b60c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -45,12 +45,17 @@
     private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
     private val burnInHelperWrapper: BurnInHelperWrapper,
 ) {
+    data class PreviewMode(
+        val isInPreviewMode: Boolean = false,
+        val shouldHighlightSelectedAffordance: Boolean = false,
+    )
+
     /**
      * Whether this view-model instance is powering the preview experience that renders exclusively
      * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
      * experience.
      */
-    private val isInPreviewMode = MutableStateFlow(false)
+    private val previewMode = MutableStateFlow(PreviewMode())
 
     /**
      * ID of the slot that's currently selected in the preview that renders exclusively in the
@@ -87,8 +92,8 @@
         keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
     /** An observable for the alpha level for the entire bottom area. */
     val alpha: Flow<Float> =
-        isInPreviewMode.flatMapLatest { isInPreviewMode ->
-            if (isInPreviewMode) {
+        previewMode.flatMapLatest {
+            if (it.isInPreviewMode) {
                 flowOf(1f)
             } else {
                 bottomAreaInteractor.alpha.distinctUntilChanged()
@@ -129,9 +134,18 @@
      * lock screen.
      *
      * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
+     * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be
+     * highlighted (while all others are dimmed to make the selected one stand out).
      */
-    fun enablePreviewMode(initiallySelectedSlotId: String?) {
-        isInPreviewMode.value = true
+    fun enablePreviewMode(
+        initiallySelectedSlotId: String?,
+        shouldHighlightSelectedAffordance: Boolean,
+    ) {
+        previewMode.value =
+            PreviewMode(
+                isInPreviewMode = true,
+                shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+            )
         onPreviewSlotSelected(
             initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
         )
@@ -150,9 +164,9 @@
     private fun button(
         position: KeyguardQuickAffordancePosition
     ): Flow<KeyguardQuickAffordanceViewModel> {
-        return isInPreviewMode.flatMapLatest { isInPreviewMode ->
+        return previewMode.flatMapLatest { previewMode ->
             combine(
-                    if (isInPreviewMode) {
+                    if (previewMode.isInPreviewMode) {
                         quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
                     } else {
                         quickAffordanceInteractor.quickAffordance(position = position)
@@ -161,11 +175,18 @@
                     areQuickAffordancesFullyOpaque,
                     selectedPreviewSlotId,
                 ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId ->
+                    val isSelected = selectedPreviewSlotId == position.toSlotId()
                     model.toViewModel(
-                        animateReveal = !isInPreviewMode && animateReveal,
-                        isClickable = isFullyOpaque && !isInPreviewMode,
+                        animateReveal = !previewMode.isInPreviewMode && animateReveal,
+                        isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
                         isSelected =
-                            (isInPreviewMode && selectedPreviewSlotId == position.toSlotId()),
+                            previewMode.isInPreviewMode &&
+                                previewMode.shouldHighlightSelectedAffordance &&
+                                isSelected,
+                        isDimmed =
+                            previewMode.isInPreviewMode &&
+                                previewMode.shouldHighlightSelectedAffordance &&
+                                !isSelected,
                     )
                 }
                 .distinctUntilChanged()
@@ -176,6 +197,7 @@
         animateReveal: Boolean,
         isClickable: Boolean,
         isSelected: Boolean,
+        isDimmed: Boolean,
     ): KeyguardQuickAffordanceViewModel {
         return when (this) {
             is KeyguardQuickAffordanceModel.Visible ->
@@ -194,6 +216,7 @@
                     isActivated = activationState is ActivationState.Active,
                     isSelected = isSelected,
                     useLongPress = quickAffordanceInteractor.useLongPress,
+                    isDimmed = isDimmed,
                 )
             is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index c6002d6..b8b3a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,12 +20,10 @@
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 
 /** Models UI state for the lock screen bouncer; handles user input. */
@@ -41,13 +39,12 @@
     /** Observe on bouncer visibility. */
     val isBouncerVisible: Flow<Boolean> = interactor.isVisible
 
+    /** Can the user interact with the view? */
+    val isInteractable: Flow<Boolean> = interactor.isInteractable
+
     /** Observe whether bouncer is showing. */
     val show: Flow<KeyguardBouncerModel> = interactor.show
 
-    /** Observe visible expansion when bouncer is showing. */
-    val showWithFullExpansion: Flow<KeyguardBouncerModel> =
-        interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
-
     /** Observe whether bouncer is hiding. */
     val hide: Flow<Unit> = interactor.hide
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
new file mode 100644
index 0000000..d896390
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Models UI state to support the lock screen long-press feature. */
+class KeyguardLongPressViewModel
+@Inject
+constructor(
+    private val interactor: KeyguardLongPressInteractor,
+) {
+
+    /** Whether the long-press handling feature should be enabled. */
+    val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled
+
+    /** View-model for a menu that should be shown; `null` when no menu should be shown. */
+    val menu: Flow<KeyguardSettingsPopupMenuViewModel?> =
+        interactor.menu.map { model ->
+            model?.let {
+                KeyguardSettingsPopupMenuViewModel(
+                    icon =
+                        Icon.Resource(
+                            res = R.drawable.ic_settings,
+                            contentDescription = null,
+                        ),
+                    text =
+                        Text.Resource(
+                            res = R.string.lock_screen_settings,
+                        ),
+                    position = model.position,
+                    onClicked = model.onClicked,
+                    onDismissed = model.onDismissed,
+                )
+            }
+        }
+
+    /** Notifies that the user has long-pressed on the lock screen. */
+    fun onLongPress(
+        x: Int,
+        y: Int,
+    ) {
+        interactor.onLongPress(
+            x = x,
+            y = y,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index cf3a6da..cb68a82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -31,6 +31,7 @@
     val isActivated: Boolean = false,
     val isSelected: Boolean = false,
     val useLongPress: Boolean = false,
+    val isDimmed: Boolean = false,
 ) {
     data class OnClickedParameters(
         val configKey: String,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt
new file mode 100644
index 0000000..0571b05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Position
+import com.android.systemui.common.shared.model.Text
+
+/** Models the UI state of a keyguard settings popup menu. */
+data class KeyguardSettingsPopupMenuViewModel(
+    val icon: Icon,
+    val text: Text,
+    /** Where the menu should be anchored, roughly in screen space. */
+    val position: Position,
+    /** Callback to invoke when the menu gets clicked by the user. */
+    val onClicked: () -> Unit,
+    /** Callback to invoke when the menu gets dismissed by the user. */
+    val onDismissed: () -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
index e05adbd..bc9dc4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt
@@ -20,15 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 
 /**
  * Breaks down LOCKSCREEN->DREAMING transition into discrete steps for corresponding views to
@@ -40,35 +35,32 @@
 constructor(
     private val interactor: KeyguardTransitionInteractor,
 ) {
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_DREAMING_DURATION,
+            transitionFlow = interactor.lockscreenToDreamingTransition,
+        )
 
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return merge(
-            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-                (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
-            },
-            // On end, reset the translation to 0
-            interactor.lockscreenToDreamingTransition
-                .filter { it.transitionState == FINISHED || it.transitionState == CANCELED }
-                .map { 0f }
+        return transitionAnimation.createFlow(
+            duration = 500.milliseconds,
+            onStep = { it * translatePx },
+            // Reset on cancel or finish
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_ACCELERATE,
         )
     }
 
     /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.lockscreenToDreamingTransition,
-            params,
-            totalDuration = TO_DREAMING_DURATION
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 250.milliseconds,
+            onStep = { 1f - it },
         )
-    }
 
     companion object {
         @JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds
-
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = 500.milliseconds)
-        val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 22d292e..a60665a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -20,14 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
-import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
 
 /**
  * Breaks down LOCKSCREEN->OCCLUDED transition into discrete steps for corresponding views to
@@ -39,33 +35,28 @@
 constructor(
     private val interactor: KeyguardTransitionInteractor,
 ) {
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_OCCLUDED_DURATION,
+            transitionFlow = interactor.lockscreenToOccludedTransition,
+        )
+
+    /** Lockscreen views alpha */
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            duration = 250.milliseconds,
+            onStep = { 1f - it },
+        )
 
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return merge(
-            flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-                (EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx)
-            },
-            // On end, reset the translation to 0
-            interactor.lockscreenToOccludedTransition
-                .filter { step -> step.transitionState == TransitionState.FINISHED }
-                .map { 0f }
+        return transitionAnimation.createFlow(
+            duration = TO_OCCLUDED_DURATION,
+            onStep = { value -> value * translatePx },
+            // Reset on cancel or finish
+            onFinish = { 0f },
+            onCancel = { 0f },
+            interpolator = EMPHASIZED_ACCELERATE,
         )
     }
-
-    /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA).map { 1f - it }
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.lockscreenToOccludedTransition,
-            params,
-            totalDuration = TO_OCCLUDED_DURATION
-        )
-    }
-
-    companion object {
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_OCCLUDED_DURATION)
-        val LOCKSCREEN_ALPHA = AnimationParams(duration = 250.milliseconds)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index e804562..5770f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -20,11 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -36,28 +35,26 @@
 constructor(
     private val interactor: KeyguardTransitionInteractor,
 ) {
+    private val transitionAnimation =
+        KeyguardTransitionAnimationFlow(
+            transitionDuration = TO_LOCKSCREEN_DURATION,
+            transitionFlow = interactor.occludedToLockscreenTransition,
+        )
+
     /** Lockscreen views y-translation */
     fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
-        return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
-            -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
-        }
-    }
-
-    /** Lockscreen views alpha */
-    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
-
-    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
-        return interactor.transitionStepAnimation(
-            interactor.occludedToLockscreenTransition,
-            params,
-            totalDuration = TO_LOCKSCREEN_DURATION
+        return transitionAnimation.createFlow(
+            duration = TO_LOCKSCREEN_DURATION,
+            onStep = { value -> -translatePx + value * translatePx },
+            interpolator = EMPHASIZED_DECELERATE,
         )
     }
 
-    companion object {
-        @JvmField val LOCKSCREEN_ANIMATION_DURATION_MS = TO_LOCKSCREEN_DURATION.inWholeMilliseconds
-        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
-        val LOCKSCREEN_ALPHA =
-            AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
-    }
+    /** Lockscreen views alpha */
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.createFlow(
+            startTime = 233.milliseconds,
+            duration = 250.milliseconds,
+            onStep = { it },
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
new file mode 100644
index 0000000..5acaa46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.systemui.log
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.graphics.RectF
+import androidx.core.graphics.toRectF
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.ScreenDecorationsLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.ERROR
+import javax.inject.Inject
+
+private const val TAG = "ScreenDecorationsLog"
+
+/**
+ * Helper class for logging for [com.android.systemui.ScreenDecorations]
+ *
+ * To enable logcat echoing for an entire buffer:
+ *
+ * ```
+ *   adb shell settings put global systemui/buffer/ScreenDecorationsLog <logLevel>
+ *
+ * ```
+ */
+@SysUISingleton
+class ScreenDecorationsLogger
+@Inject
+constructor(
+    @ScreenDecorationsLog private val logBuffer: LogBuffer,
+) {
+    fun cameraProtectionBoundsForScanningOverlay(bounds: Rect) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = bounds.toShortString() },
+            { "Face scanning overlay present camera protection bounds: $str1" }
+        )
+    }
+
+    fun hwcLayerCameraProtectionBounds(bounds: Rect) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            { str1 = bounds.toShortString() },
+            { "Hwc layer present camera protection bounds: $str1" }
+        )
+    }
+
+    fun dcvCameraBounds(id: Int, bounds: Rect) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = bounds.toShortString()
+                int1 = id
+            },
+            { "DisplayCutoutView id=$int1 present, camera protection bounds: $str1" }
+        )
+    }
+
+    fun cutoutViewNotInitialized() {
+        logBuffer.log(TAG, ERROR, "CutoutView not initialized showCameraProtection")
+    }
+
+    fun boundingRect(boundingRectangle: RectF, context: String) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = context
+                str2 = boundingRectangle.toShortString()
+            },
+            { "Bounding rect $str1 : $str2" }
+        )
+    }
+
+    fun boundingRect(boundingRectangle: Rect, context: String) {
+        boundingRect(boundingRectangle.toRectF(), context)
+    }
+
+    fun onMeasureDimensions(
+        widthMeasureSpec: Int,
+        heightMeasureSpec: Int,
+        measuredWidth: Int,
+        measuredHeight: Int
+    ) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                long1 = widthMeasureSpec.toLong()
+                long2 = heightMeasureSpec.toLong()
+                int1 = measuredWidth
+                int2 = measuredHeight
+            },
+            {
+                "Face scanning animation: widthMeasureSpec: $long1 measuredWidth: $int1, " +
+                    "heightMeasureSpec: $long2 measuredHeight: $int2"
+            }
+        )
+    }
+
+    fun faceSensorLocation(faceSensorLocation: Point?) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                int1 = faceSensorLocation?.y?.times(2) ?: 0
+                str1 = "$faceSensorLocation"
+            },
+            { "Reinflating view: Face sensor location: $str1, faceScanningHeight: $int1" }
+        )
+    }
+
+    fun onSensorLocationChanged() {
+        logBuffer.log(TAG, DEBUG, "AuthControllerCallback in ScreenDecorations triggered")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index d7817e1..4177480 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -191,37 +191,12 @@
                 false /* systrace */);
     }
 
-    /**
-     * Provides a logging buffer for logs related to swiping away the status bar while in immersive
-     * mode. See {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
-     */
+    /** Provides a logging buffer for logs related to swipe up gestures. */
     @Provides
     @SysUISingleton
-    @SwipeStatusBarAwayLog
-    public static LogBuffer provideSwipeAwayGestureLogBuffer(LogBufferFactory factory) {
-        return factory.create("SwipeStatusBarAwayLog", 30);
-    }
-
-    /**
-     * Provides a logging buffer for logs related to the media tap-to-transfer chip on the sender
-     * device. See {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}.
-     */
-    @Provides
-    @SysUISingleton
-    @MediaTttSenderLogBuffer
-    public static LogBuffer provideMediaTttSenderLogBuffer(LogBufferFactory factory) {
-        return factory.create("MediaTttSender", 20);
-    }
-
-    /**
-     * Provides a logging buffer for logs related to the media tap-to-transfer chip on the receiver
-     * device. See {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}.
-     */
-    @Provides
-    @SysUISingleton
-    @MediaTttReceiverLogBuffer
-    public static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) {
-        return factory.create("MediaTttReceiver", 20);
+    @SwipeUpLog
+    public static LogBuffer provideSwipeUpLogBuffer(LogBufferFactory factory) {
+        return factory.create("SwipeUpLog", 30);
     }
 
     /**
@@ -375,6 +350,16 @@
     }
 
     /**
+     * Provides a {@link LogBuffer} for use by {@link com.android.systemui.ScreenDecorations}.
+     */
+    @Provides
+    @SysUISingleton
+    @ScreenDecorationsLog
+    public static LogBuffer provideScreenDecorationsLog(LogBufferFactory factory) {
+        return factory.create("ScreenDecorationsLog", 200);
+    }
+
+    /**
      * Provides a {@link LogBuffer} for bluetooth-related logs.
      */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
index 67733e9..de2a8b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ScreenDecorationsLog.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -11,15 +11,15 @@
  * 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
+ * limitations under the License.
  */
-package com.android.systemui.keyguard.shared.model
 
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
+package com.android.systemui.log.dagger
 
-/** Animation parameters */
-data class AnimationParams(
-    val startTime: Duration = 0.milliseconds,
-    val duration: Duration,
-)
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for ScreenDecorations added by SysUI. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ScreenDecorationsLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
rename to packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
index 4c276e2..d58b538 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeUpLog.java
@@ -27,10 +27,10 @@
 
 /**
  * A {@link LogBuffer} for
- * {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ * {@link com.android.systemui.statusbar.gesture.SwipeUpGestureLogger}.
  */
 @Qualifier
 @Documented
 @Retention(RUNTIME)
-public @interface SwipeStatusBarAwayLog {
+public @interface SwipeUpLog {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index a692ad7..52d4171 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -28,12 +28,15 @@
 import android.os.UserHandle
 import android.view.ViewGroup
 import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
 import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
 import com.android.internal.app.ChooserActivity
 import com.android.internal.app.ResolverListController
 import com.android.internal.app.chooser.NotSelectableTargetInfo
 import com.android.internal.app.chooser.TargetInfo
 import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
@@ -47,6 +50,7 @@
 class MediaProjectionAppSelectorActivity(
     private val componentFactory: MediaProjectionAppSelectorComponent.Factory,
     private val activityLauncher: AsyncActivityLauncher,
+    private val featureFlags: FeatureFlags,
     /** This is used to override the dependency in a screenshot test */
     @VisibleForTesting
     private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
@@ -56,7 +60,8 @@
     constructor(
         componentFactory: MediaProjectionAppSelectorComponent.Factory,
         activityLauncher: AsyncActivityLauncher,
-    ) : this(componentFactory, activityLauncher, null)
+        featureFlags: FeatureFlags
+    ) : this(componentFactory, activityLauncher, featureFlags, listControllerFactory = null)
 
     private lateinit var configurationController: ConfigurationController
     private lateinit var controller: MediaProjectionAppSelectorController
@@ -91,6 +96,13 @@
 
     override fun appliedThemeResId(): Int = R.style.Theme_SystemUI_MediaProjectionAppSelector
 
+    override fun createBlockerEmptyStateProvider(): EmptyStateProvider =
+        if (featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
+            component.emptyStateProvider
+        } else {
+            super.createBlockerEmptyStateProvider()
+        }
+
     override fun createListController(userHandle: UserHandle): ResolverListController =
         listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index d830fc4..c4e76b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -45,33 +45,46 @@
 import android.util.Log;
 import android.view.Window;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
 import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
 import com.android.systemui.screenrecord.ScreenShareOption;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.Utils;
 
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
 public class MediaProjectionPermissionActivity extends Activity
         implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
     private static final String TAG = "MediaProjectionPermissionActivity";
     private static final float MAX_APP_NAME_SIZE_PX = 500f;
     private static final String ELLIPSIS = "\u2026";
 
+    private final FeatureFlags mFeatureFlags;
+    private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
+
     private String mPackageName;
     private int mUid;
     private IMediaProjectionManager mService;
-    private FeatureFlags mFeatureFlags;
 
     private AlertDialog mDialog;
 
+    @Inject
+    public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
+            Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
+        mFeatureFlags = featureFlags;
+        mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
+    }
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        mFeatureFlags = Dependency.get(FeatureFlags.class);
         mPackageName = getCallingPackage();
         IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
         mService = IMediaProjectionManager.Stub.asInterface(b);
@@ -104,6 +117,12 @@
             return;
         }
 
+        if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
+            if (showScreenCaptureDisabledDialogIfNeeded()) {
+                return;
+            }
+        }
+
         TextPaint paint = new TextPaint();
         paint.setTextSize(42);
 
@@ -171,16 +190,7 @@
             mDialog = dialogBuilder.create();
         }
 
-        SystemUIDialog.registerDismissListener(mDialog);
-        SystemUIDialog.applyFlags(mDialog);
-        SystemUIDialog.setDialogSize(mDialog);
-
-        mDialog.setOnCancelListener(this);
-        mDialog.create();
-        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
-
-        final Window w = mDialog.getWindow();
-        w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        setUpDialog(mDialog);
 
         mDialog.show();
     }
@@ -200,6 +210,32 @@
         }
     }
 
+    private void setUpDialog(AlertDialog dialog) {
+        SystemUIDialog.registerDismissListener(dialog);
+        SystemUIDialog.applyFlags(dialog);
+        SystemUIDialog.setDialogSize(dialog);
+
+        dialog.setOnCancelListener(this);
+        dialog.create();
+        dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+
+        final Window w = dialog.getWindow();
+        w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+    }
+
+    private boolean showScreenCaptureDisabledDialogIfNeeded() {
+        final UserHandle hostUserHandle = getHostUserHandle();
+        if (mScreenCaptureDevicePolicyResolver.get()
+                .isScreenCaptureCompletelyDisabled(hostUserHandle)) {
+            AlertDialog dialog = new ScreenCaptureDisabledDialog(this);
+            setUpDialog(dialog);
+            dialog.show();
+            return true;
+        }
+
+        return false;
+    }
+
     private void grantMediaProjectionPermission(int screenShareMode) {
         try {
             if (screenShareMode == ENTIRE_SCREEN) {
@@ -211,7 +247,7 @@
                 intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
                         projection.asBinder());
                 intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
-                        UserHandle.getUserHandleForUid(getLaunchedFromUid()));
+                        getHostUserHandle());
                 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
 
                 // Start activity from the current foreground user to avoid creating a separate
@@ -230,6 +266,10 @@
         }
     }
 
+    private UserHandle getHostUserHandle() {
+        return UserHandle.getUserHandleForUid(getLaunchedFromUid());
+    }
+
     private IMediaProjection createProjection(int uid, String packageName) throws RemoteException {
         return mService.createProjection(uid, packageName,
                 MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index be18cbe..b7a2522 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -92,6 +92,9 @@
 
     /** Whether explicit indicator exists */
     val isExplicit: Boolean = false,
+
+    /** Track progress (0 - 1) to display for players where [resumption] is true */
+    val resumeProgress: Double? = null,
 ) {
     companion object {
         /** Media is playing on the local device */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index bba5f35..a057c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -238,6 +238,24 @@
     }
 
     /**
+     * Set the progress to a fixed percentage value that cannot be changed by the user.
+     *
+     * @param percent value between 0 and 1
+     */
+    fun updateStaticProgress(percent: Double) {
+        val position = (percent * 100).toInt()
+        _data =
+            Progress(
+                enabled = true,
+                seekAvailable = false,
+                playing = false,
+                scrubbing = false,
+                elapsedTime = position,
+                duration = 100,
+            )
+    }
+
+    /**
      * Puts the seek bar into a resumption state.
      *
      * This should be called when the media session behind the controller has been destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 1a10b18..70f2dee 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -20,7 +20,9 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
+import android.widget.SeekBar
 import android.widget.TextView
+import com.android.internal.widget.CachingIconView
 import com.android.systemui.R
 import com.android.systemui.media.controls.models.GutsViewHolder
 import com.android.systemui.media.controls.ui.IlluminationDrawable
@@ -29,18 +31,16 @@
 private const val TAG = "RecommendationViewHolder"
 
 /** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View) {
+class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
 
     val recommendations = itemView as TransitionLayout
 
     // Recommendation screen
-    val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
-    val mediaCoverItems =
-        listOf<ImageView>(
-            itemView.requireViewById(R.id.media_cover1),
-            itemView.requireViewById(R.id.media_cover2),
-            itemView.requireViewById(R.id.media_cover3)
-        )
+    lateinit var cardIcon: ImageView
+    lateinit var mediaAppIcons: List<CachingIconView>
+    lateinit var mediaProgressBars: List<SeekBar>
+    lateinit var cardTitle: TextView
+
     val mediaCoverContainers =
         listOf<ViewGroup>(
             itemView.requireViewById(R.id.media_cover1_container),
@@ -48,21 +48,52 @@
             itemView.requireViewById(R.id.media_cover3_container)
         )
     val mediaTitles: List<TextView> =
-        listOf(
-            itemView.requireViewById(R.id.media_title1),
-            itemView.requireViewById(R.id.media_title2),
-            itemView.requireViewById(R.id.media_title3)
-        )
+        if (updatedView) {
+            mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
+        } else {
+            listOf(
+                itemView.requireViewById(R.id.media_title1),
+                itemView.requireViewById(R.id.media_title2),
+                itemView.requireViewById(R.id.media_title3)
+            )
+        }
     val mediaSubtitles: List<TextView> =
-        listOf(
-            itemView.requireViewById(R.id.media_subtitle1),
-            itemView.requireViewById(R.id.media_subtitle2),
-            itemView.requireViewById(R.id.media_subtitle3)
-        )
+        if (updatedView) {
+            mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
+        } else {
+            listOf(
+                itemView.requireViewById(R.id.media_subtitle1),
+                itemView.requireViewById(R.id.media_subtitle2),
+                itemView.requireViewById(R.id.media_subtitle3)
+            )
+        }
 
+    val mediaCoverItems: List<ImageView> =
+        if (updatedView) {
+            mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
+        } else {
+            listOf(
+                itemView.requireViewById(R.id.media_cover1),
+                itemView.requireViewById(R.id.media_cover2),
+                itemView.requireViewById(R.id.media_cover3)
+            )
+        }
     val gutsViewHolder = GutsViewHolder(itemView)
 
     init {
+        if (updatedView) {
+            mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
+            cardTitle = itemView.requireViewById(R.id.media_rec_title)
+            mediaProgressBars =
+                mediaCoverContainers.map {
+                    it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
+                        // Media playback is in the direction of tape, not time, so it stays LTR
+                        layoutDirection = View.LAYOUT_DIRECTION_LTR
+                    }
+                }
+        } else {
+            cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
+        }
         (recommendations.background as IlluminationDrawable).let { background ->
             mediaCoverContainers.forEach { background.registerLightSource(it) }
             background.registerLightSource(gutsViewHolder.cancel)
@@ -83,36 +114,52 @@
          * @param parent Parent of inflated view.
          */
         @JvmStatic
-        fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
+        fun create(
+            inflater: LayoutInflater,
+            parent: ViewGroup,
+            updatedView: Boolean,
+        ): RecommendationViewHolder {
             val itemView =
-                inflater.inflate(
-                    R.layout.media_smartspace_recommendations,
-                    parent,
-                    false /* attachToRoot */
-                )
+                if (updatedView) {
+                    inflater.inflate(
+                        R.layout.media_recommendations,
+                        parent,
+                        false /* attachToRoot */
+                    )
+                } else {
+                    inflater.inflate(
+                        R.layout.media_smartspace_recommendations,
+                        parent,
+                        false /* attachToRoot */
+                    )
+                }
             // Because this media view (a TransitionLayout) is used to measure and layout the views
             // in various states before being attached to its parent, we can't depend on the default
             // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
             itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
-            return RecommendationViewHolder(itemView)
+            return RecommendationViewHolder(itemView, updatedView)
         }
 
         // Res Ids for the control components on the recommendation view.
         val controlsIds =
             setOf(
                 R.id.recommendation_card_icon,
+                R.id.media_rec_title,
                 R.id.media_cover1,
                 R.id.media_cover2,
                 R.id.media_cover3,
+                R.id.media_cover,
                 R.id.media_cover1_container,
                 R.id.media_cover2_container,
                 R.id.media_cover3_container,
                 R.id.media_title1,
                 R.id.media_title2,
                 R.id.media_title3,
+                R.id.media_title,
                 R.id.media_subtitle1,
                 R.id.media_subtitle2,
-                R.id.media_subtitle3
+                R.id.media_subtitle3,
+                R.id.media_subtitle,
             )
 
         val mediaTitlesAndSubtitlesIds =
@@ -120,9 +167,11 @@
                 R.id.media_title1,
                 R.id.media_title2,
                 R.id.media_title3,
+                R.id.media_title,
                 R.id.media_subtitle1,
                 R.id.media_subtitle2,
-                R.id.media_subtitle3
+                R.id.media_subtitle3,
+                R.id.media_subtitle,
             )
 
         val mediaContainersIds =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
index 1df42c6..dc7a4f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
@@ -41,10 +41,12 @@
     val recommendations: List<SmartspaceAction>,
     /** Intent for the user's initiated dismissal. */
     val dismissIntent: Intent?,
-    /** The timestamp in milliseconds that headphone is connected. */
+    /** The timestamp in milliseconds that the card was generated */
     val headphoneConnectionTimeMillis: Long,
     /** Instance ID for [MediaUiEventLogger] */
-    val instanceId: InstanceId
+    val instanceId: InstanceId,
+    /** The timestamp in milliseconds indicating when the card should be removed */
+    val expiryTimeMs: Long,
 ) {
     /**
      * Indicates if all the data is valid.
@@ -86,5 +88,12 @@
     }
 }
 
+/** Key for extras [SmartspaceMediaData.cardAction] indicating why the card was sent */
+const val EXTRA_KEY_TRIGGER_SOURCE = "MEDIA_RECOMMENDATION_TRIGGER_SOURCE"
+/** Value for [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent on headphone connection */
+const val EXTRA_VALUE_TRIGGER_HEADPHONE = "HEADPHONE_CONNECTION"
+/** Value for key [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent as a regular update */
+const val EXTRA_VALUE_TRIGGER_PERIODIC = "PERIODIC_TRIGGER"
+
 const val NUM_REQUIRED_RECOMMENDATIONS = 3
 private val TAG = SmartspaceMediaData::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index cf71d67..27f7b97 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -66,7 +67,8 @@
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     @Main private val executor: Executor,
     private val systemClock: SystemClock,
-    private val logger: MediaUiEventLogger
+    private val logger: MediaUiEventLogger,
+    private val mediaFlags: MediaFlags,
 ) : MediaDataManager.Listener {
     private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
     internal val listeners: Set<MediaDataManager.Listener>
@@ -121,7 +123,9 @@
         data: SmartspaceMediaData,
         shouldPrioritize: Boolean
     ) {
-        if (!data.isActive) {
+        // With persistent recommendation card, we could get a background update while inactive
+        // Otherwise, consider it an invalid update
+        if (!data.isActive && !mediaFlags.isPersistentSsCardEnabled()) {
             Log.d(TAG, "Inactive recommendation data. Skip triggering.")
             return
         }
@@ -141,7 +145,7 @@
             }
         }
 
-        val shouldReactivate = !hasActiveMedia() && hasAnyMedia()
+        val shouldReactivate = !hasActiveMedia() && hasAnyMedia() && data.isActive
 
         if (timeSinceActive < smartspaceMaxAgeMillis) {
             // It could happen there are existing active media resume cards, then we don't need to
@@ -169,7 +173,7 @@
                     )
                 }
             }
-        } else {
+        } else if (data.isActive) {
             // Mark to prioritize Smartspace card if no recent media.
             shouldPrioritizeMutable = true
         }
@@ -252,7 +256,7 @@
             if (dismissIntent == null) {
                 Log.w(
                     TAG,
-                    "Cannot create dismiss action click action: " + "extras missing dismiss_intent."
+                    "Cannot create dismiss action click action: extras missing dismiss_intent."
                 )
             } else if (
                 dismissIntent.getComponent() != null &&
@@ -264,15 +268,21 @@
             } else {
                 broadcastSender.sendBroadcast(dismissIntent)
             }
-            smartspaceMediaData =
-                EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                    targetId = smartspaceMediaData.targetId,
-                    instanceId = smartspaceMediaData.instanceId
+
+            if (mediaFlags.isPersistentSsCardEnabled()) {
+                smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
+                mediaDataManager.setRecommendationInactive(smartspaceMediaData.targetId)
+            } else {
+                smartspaceMediaData =
+                    EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                        targetId = smartspaceMediaData.targetId,
+                        instanceId = smartspaceMediaData.instanceId,
+                    )
+                mediaDataManager.dismissSmartspaceRecommendation(
+                    smartspaceMediaData.targetId,
+                    delay = 0L,
                 )
-            mediaDataManager.dismissSmartspaceRecommendation(
-                smartspaceMediaData.targetId,
-                delay = 0L
-            )
+            }
         }
     }
 
@@ -283,8 +293,15 @@
                 (smartspaceMediaData.isValid() || reactivatedKey != null))
 
     /** Are there any media entries we should display? */
-    fun hasAnyMediaOrRecommendation() =
-        userEntries.isNotEmpty() || (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
+    fun hasAnyMediaOrRecommendation(): Boolean {
+        val hasSmartspace =
+            if (mediaFlags.isPersistentSsCardEnabled()) {
+                smartspaceMediaData.isValid()
+            } else {
+                smartspaceMediaData.isActive && smartspaceMediaData.isValid()
+            }
+        return userEntries.isNotEmpty() || hasSmartspace
+    }
 
     /** Are there any media notifications active (excluding the recommendation)? */
     fun hasActiveMedia() = userEntries.any { it.value.active }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index a132797..0a94803 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -49,7 +49,6 @@
 import android.text.TextUtils
 import android.util.Log
 import androidx.media.utils.MediaConstants
-import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.logging.InstanceId
 import com.android.systemui.Dumpable
 import com.android.systemui.R
@@ -63,10 +62,13 @@
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.player.MediaDeviceData
 import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
 import com.android.systemui.media.controls.resume.MediaResumeListener
 import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaDataUtils
 import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
@@ -118,7 +120,6 @@
         appUid = Process.INVALID_UID
     )
 
-@VisibleForTesting
 internal val EMPTY_SMARTSPACE_MEDIA_DATA =
     SmartspaceMediaData(
         targetId = "INVALID",
@@ -128,7 +129,8 @@
         recommendations = emptyList(),
         dismissIntent = null,
         headphoneConnectionTimeMillis = 0,
-        instanceId = InstanceId.fakeInstanceId(-1)
+        instanceId = InstanceId.fakeInstanceId(-1),
+        expiryTimeMs = 0,
     )
 
 fun isMediaNotification(sbn: StatusBarNotification): Boolean {
@@ -303,6 +305,7 @@
         mediaTimeoutListener.stateCallback = { key: String, state: PlaybackState ->
             updateState(key, state)
         }
+        mediaTimeoutListener.sessionCallback = { key: String -> onSessionDestroyed(key) }
         mediaResumeListener.setManager(this)
         mediaDataFilter.mediaDataManager = this
 
@@ -546,6 +549,11 @@
             if (DEBUG) Log.d(TAG, "Updating $key timedOut: $timedOut")
             onMediaDataLoaded(key, key, it)
         }
+
+        if (key == smartspaceMediaData.targetId) {
+            if (DEBUG) Log.d(TAG, "smartspace card expired")
+            dismissSmartspaceRecommendation(key, delay = 0L)
+        }
     }
 
     /** Called when the player's [PlaybackState] has been updated with new actions and/or state */
@@ -603,8 +611,8 @@
     }
 
     /**
-     * Called whenever the recommendation has been expired, or swiped from QQS. This will make the
-     * recommendation view to not be shown anymore during this headphone connection session.
+     * Called whenever the recommendation has been expired or removed by the user. This will remove
+     * the recommendation card entirely from the carousel.
      */
     fun dismissSmartspaceRecommendation(key: String, delay: Long) {
         if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) {
@@ -626,6 +634,23 @@
         )
     }
 
+    /** Called when the recommendation card should no longer be visible in QQS or lockscreen */
+    fun setRecommendationInactive(key: String) {
+        if (!mediaFlags.isPersistentSsCardEnabled()) {
+            Log.e(TAG, "Only persistent recommendation can be inactive!")
+            return
+        }
+        if (DEBUG) Log.d(TAG, "Setting smartspace recommendation inactive")
+
+        if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) {
+            // If this doesn't match, or we've already invalidated the data, no action needed
+            return
+        }
+
+        smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
+        notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
+    }
+
     private fun loadMediaDataInBgForResumption(
         userId: Int,
         desc: MediaDescription,
@@ -666,6 +691,11 @@
                 MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT &&
                 mediaFlags.isExplicitIndicatorEnabled()
 
+        val progress =
+            if (mediaFlags.isResumeProgressEnabled()) {
+                MediaDataUtils.getDescriptionProgress(desc.extras)
+            } else null
+
         val mediaAction = getResumeMediaAction(resumeAction)
         val lastActive = systemClock.elapsedRealtime()
         foregroundExecutor.execute {
@@ -696,6 +726,7 @@
                     instanceId = instanceId,
                     appUid = appUid,
                     isExplicit = isExplicit,
+                    resumeProgress = progress,
                 )
             )
         }
@@ -1257,12 +1288,25 @@
                 if (DEBUG) {
                     Log.d(TAG, "Set Smartspace media to be inactive for the data update")
                 }
-                smartspaceMediaData =
-                    EMPTY_SMARTSPACE_MEDIA_DATA.copy(
-                        targetId = smartspaceMediaData.targetId,
-                        instanceId = smartspaceMediaData.instanceId
+                if (mediaFlags.isPersistentSsCardEnabled()) {
+                    // Smartspace uses this signal to hide the card (e.g. when it expires or user
+                    // disconnects headphones), so treat as setting inactive when flag is on
+                    smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
+                    notifySmartspaceMediaDataLoaded(
+                        smartspaceMediaData.targetId,
+                        smartspaceMediaData,
                     )
-                notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
+                } else {
+                    smartspaceMediaData =
+                        EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                            targetId = smartspaceMediaData.targetId,
+                            instanceId = smartspaceMediaData.instanceId,
+                        )
+                    notifySmartspaceMediaDataRemoved(
+                        smartspaceMediaData.targetId,
+                        immediately = false,
+                    )
+                }
             }
             1 -> {
                 val newMediaTarget = mediaTargets.get(0)
@@ -1271,7 +1315,7 @@
                     return
                 }
                 if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
-                smartspaceMediaData = toSmartspaceMediaData(newMediaTarget, isActive = true)
+                smartspaceMediaData = toSmartspaceMediaData(newMediaTarget)
                 notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
             }
             else -> {
@@ -1280,7 +1324,7 @@
                 Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
                 notifySmartspaceMediaDataRemoved(
                     smartspaceMediaData.targetId,
-                    false /* immediately */
+                    immediately = false,
                 )
                 smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
             }
@@ -1289,45 +1333,106 @@
 
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
-        val removed = mediaEntries.remove(key)
-        if (useMediaResumption && removed?.resumeAction != null && removed.isLocalSession()) {
-            Log.d(TAG, "Not removing $key because resumable")
-            // Move to resume key (aka package name) if that key doesn't already exist.
-            val resumeAction = getResumeMediaAction(removed.resumeAction!!)
-            val updated =
-                removed.copy(
-                    token = null,
-                    actions = listOf(resumeAction),
-                    semanticActions = MediaButton(playOrPause = resumeAction),
-                    actionsToShowInCompact = listOf(0),
-                    active = false,
-                    resumption = true,
-                    isPlaying = false,
-                    isClearable = true
-                )
-            val pkg = removed.packageName
-            val migrate = mediaEntries.put(pkg, updated) == null
-            // Notify listeners of "new" controls when migrating or removed and update when not
-            if (migrate) {
-                notifyMediaDataLoaded(pkg, key, updated)
-            } else {
-                // Since packageName is used for the key of the resumption controls, it is
-                // possible that another notification has already been reused for the resumption
-                // controls of this package. In this case, rather than renaming this player as
-                // packageName, just remove it and then send a update to the existing resumption
-                // controls.
-                notifyMediaDataRemoved(key)
-                notifyMediaDataLoaded(pkg, pkg, updated)
-            }
-            logger.logActiveConvertedToResume(updated.appUid, pkg, updated.instanceId)
-            return
-        }
-        if (removed != null) {
+        val removed = mediaEntries.remove(key) ?: return
+
+        if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
+            convertToResumePlayer(removed)
+        } else if (mediaFlags.isRetainingPlayersEnabled()) {
+            handlePossibleRemoval(removed, notificationRemoved = true)
+        } else {
             notifyMediaDataRemoved(key)
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
         }
     }
 
+    private fun onSessionDestroyed(key: String) {
+        if (!mediaFlags.isRetainingPlayersEnabled()) return
+
+        if (DEBUG) Log.d(TAG, "session destroyed for $key")
+        val entry = mediaEntries.remove(key) ?: return
+        // Clear token since the session is no longer valid
+        val updated = entry.copy(token = null)
+        handlePossibleRemoval(updated)
+    }
+
+    /**
+     * Convert to resume state if the player is no longer valid and active, then notify listeners
+     * that the data was updated. Does not convert to resume state if the player is still valid, or
+     * if it was removed before becoming inactive. (Assumes that [removed] was removed from
+     * [mediaEntries] before this function was called)
+     */
+    private fun handlePossibleRemoval(removed: MediaData, notificationRemoved: Boolean = false) {
+        val key = removed.notificationKey!!
+        val hasSession = removed.token != null
+        if (hasSession && removed.semanticActions != null) {
+            // The app was using session actions, and the session is still valid: keep player
+            if (DEBUG) Log.d(TAG, "Notification removed but using session actions $key")
+            mediaEntries.put(key, removed)
+            notifyMediaDataLoaded(key, key, removed)
+        } else if (!notificationRemoved && removed.semanticActions == null) {
+            // The app was using notification actions, and notif wasn't removed yet: keep player
+            if (DEBUG) Log.d(TAG, "Session destroyed but using notification actions $key")
+            mediaEntries.put(key, removed)
+            notifyMediaDataLoaded(key, key, removed)
+        } else if (removed.active) {
+            // This player was still active - it didn't last long enough to time out: remove
+            if (DEBUG) Log.d(TAG, "Removing still-active player $key")
+            notifyMediaDataRemoved(key)
+            logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
+        } else {
+            // Convert to resume
+            if (DEBUG) {
+                Log.d(
+                    TAG,
+                    "Notification ($notificationRemoved) and/or session " +
+                        "($hasSession) gone for inactive player $key"
+                )
+            }
+            convertToResumePlayer(removed)
+        }
+    }
+
+    /** Set the given [MediaData] as a resume state player and notify listeners */
+    private fun convertToResumePlayer(data: MediaData) {
+        val key = data.notificationKey!!
+        if (DEBUG) Log.d(TAG, "Converting $key to resume")
+        // Move to resume key (aka package name) if that key doesn't already exist.
+        val resumeAction = data.resumeAction?.let { getResumeMediaAction(it) }
+        val actions = resumeAction?.let { listOf(resumeAction) } ?: emptyList()
+        val launcherIntent =
+            context.packageManager.getLaunchIntentForPackage(data.packageName)?.let {
+                PendingIntent.getActivity(context, 0, it, PendingIntent.FLAG_IMMUTABLE)
+            }
+        val updated =
+            data.copy(
+                token = null,
+                actions = actions,
+                semanticActions = MediaButton(playOrPause = resumeAction),
+                actionsToShowInCompact = listOf(0),
+                active = false,
+                resumption = true,
+                isPlaying = false,
+                isClearable = true,
+                clickIntent = launcherIntent,
+            )
+        val pkg = data.packageName
+        val migrate = mediaEntries.put(pkg, updated) == null
+        // Notify listeners of "new" controls when migrating or removed and update when not
+        Log.d(TAG, "migrating? $migrate from $key -> $pkg")
+        if (migrate) {
+            notifyMediaDataLoaded(key = pkg, oldKey = key, info = updated)
+        } else {
+            // Since packageName is used for the key of the resumption controls, it is
+            // possible that another notification has already been reused for the resumption
+            // controls of this package. In this case, rather than renaming this player as
+            // packageName, just remove it and then send a update to the existing resumption
+            // controls.
+            notifyMediaDataRemoved(key)
+            notifyMediaDataLoaded(key = pkg, oldKey = pkg, info = updated)
+        }
+        logger.logActiveConvertedToResume(updated.appUid, pkg, updated.instanceId)
+    }
+
     fun setMediaResumptionEnabled(isEnabled: Boolean) {
         if (useMediaResumption == isEnabled) {
             return
@@ -1425,21 +1530,28 @@
     }
 
     /**
-     * Converts the pass-in SmartspaceTarget to SmartspaceMediaData with the pass-in active status.
+     * Converts the pass-in SmartspaceTarget to SmartspaceMediaData
      *
      * @return An empty SmartspaceMediaData with the valid target Id is returned if the
      * SmartspaceTarget's data is invalid.
      */
-    private fun toSmartspaceMediaData(
-        target: SmartspaceTarget,
-        isActive: Boolean
-    ): SmartspaceMediaData {
+    private fun toSmartspaceMediaData(target: SmartspaceTarget): SmartspaceMediaData {
         var dismissIntent: Intent? = null
         if (target.baseAction != null && target.baseAction.extras != null) {
             dismissIntent =
                 target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY)
                     as Intent?
         }
+
+        val isActive =
+            when {
+                !mediaFlags.isPersistentSsCardEnabled() -> true
+                target.baseAction == null -> true
+                else ->
+                    target.baseAction.extras.getString(EXTRA_KEY_TRIGGER_SOURCE) !=
+                        EXTRA_VALUE_TRIGGER_PERIODIC
+            }
+
         packageName(target)?.let {
             return SmartspaceMediaData(
                 targetId = target.smartspaceTargetId,
@@ -1449,7 +1561,8 @@
                 recommendations = target.iconGrid,
                 dismissIntent = dismissIntent,
                 headphoneConnectionTimeMillis = target.creationTimeMillis,
-                instanceId = logger.getNewInstanceId()
+                instanceId = logger.getNewInstanceId(),
+                expiryTimeMs = target.expiryTimeMillis,
             )
         }
         return EMPTY_SMARTSPACE_MEDIA_DATA.copy(
@@ -1457,7 +1570,8 @@
             isActive = isActive,
             dismissIntent = dismissIntent,
             headphoneConnectionTimeMillis = target.creationTimeMillis,
-            instanceId = logger.getNewInstanceId()
+            instanceId = logger.getNewInstanceId(),
+            expiryTimeMs = target.expiryTimeMillis,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
index 7f5c82f..aa46b14 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
@@ -23,7 +23,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -49,10 +51,12 @@
     @Main private val mainExecutor: DelayableExecutor,
     private val logger: MediaTimeoutLogger,
     statusBarStateController: SysuiStatusBarStateController,
-    private val systemClock: SystemClock
+    private val systemClock: SystemClock,
+    private val mediaFlags: MediaFlags,
 ) : MediaDataManager.Listener {
 
     private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf()
+    private val recommendationListeners: MutableMap<String, RecommendationListener> = mutableMapOf()
 
     /**
      * Callback representing that a media object is now expired:
@@ -71,6 +75,12 @@
      */
     lateinit var stateCallback: (String, PlaybackState) -> Unit
 
+    /**
+     * Callback representing that the [MediaSession] for an active control has been destroyed
+     * @param key Media control unique identifier
+     */
+    lateinit var sessionCallback: (String) -> Unit
+
     init {
         statusBarStateController.addCallback(
             object : StatusBarStateController.StateListener {
@@ -87,6 +97,16 @@
                                 listener.doTimeout()
                             }
                         }
+
+                        recommendationListeners.forEach { (key, listener) ->
+                            if (
+                                listener.cancellation != null &&
+                                    listener.expiration <= systemClock.currentTimeMillis()
+                            ) {
+                                logger.logTimeoutCancelled(key, "Timed out while dozing")
+                                listener.doTimeout()
+                            }
+                        }
                     }
                 }
             }
@@ -149,6 +169,30 @@
         mediaListeners.remove(key)?.destroy()
     }
 
+    override fun onSmartspaceMediaDataLoaded(
+        key: String,
+        data: SmartspaceMediaData,
+        shouldPrioritize: Boolean
+    ) {
+        if (!mediaFlags.isPersistentSsCardEnabled()) return
+
+        // First check if we already have a listener
+        recommendationListeners.get(key)?.let {
+            if (!it.destroyed) {
+                it.recommendationData = data
+                return
+            }
+        }
+
+        // Otherwise, create a new one
+        recommendationListeners[key] = RecommendationListener(key, data)
+    }
+
+    override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
+        if (!mediaFlags.isPersistentSsCardEnabled()) return
+        recommendationListeners.remove(key)?.destroy()
+    }
+
     fun isTimedOut(key: String): Boolean {
         return mediaListeners[key]?.timedOut ?: false
     }
@@ -211,6 +255,7 @@
             } else {
                 // For active controls, if the session is destroyed, clean up everything since we
                 // will need to recreate it if this key is updated later
+                sessionCallback.invoke(key)
                 destroy()
             }
         }
@@ -328,4 +373,53 @@
         }
         return true
     }
+
+    /** Listens to changes in recommendation card data and schedules a timeout for its expiration */
+    private inner class RecommendationListener(var key: String, data: SmartspaceMediaData) {
+        private var timedOut = false
+        var destroyed = false
+        var expiration = Long.MAX_VALUE
+            private set
+        var cancellation: Runnable? = null
+            private set
+
+        var recommendationData: SmartspaceMediaData = data
+            set(value) {
+                destroyed = false
+                field = value
+                processUpdate()
+            }
+
+        init {
+            recommendationData = data
+        }
+
+        fun destroy() {
+            cancellation?.run()
+            cancellation = null
+            destroyed = true
+        }
+
+        private fun processUpdate() {
+            if (recommendationData.expiryTimeMs != expiration) {
+                // The expiry time changed - cancel and reschedule
+                val timeout =
+                    recommendationData.expiryTimeMs -
+                        recommendationData.headphoneConnectionTimeMillis
+                logger.logRecommendationTimeoutScheduled(key, timeout)
+                cancellation?.run()
+                cancellation = mainExecutor.executeDelayed({ doTimeout() }, timeout)
+                expiration = recommendationData.expiryTimeMs
+            }
+        }
+
+        fun doTimeout() {
+            cancellation?.run()
+            cancellation = null
+            logger.logTimeout(key)
+            timedOut = true
+            expiration = Long.MAX_VALUE
+            timeoutCallback(key, timedOut)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
index 8f3f054..f731dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
@@ -107,6 +107,17 @@
                 str1 = key
                 str2 = reason
             },
-            { "media timeout cancelled for $str1, reason: $str2" }
+            { "timeout cancelled for $str1, reason: $str2" }
+        )
+
+    fun logRecommendationTimeoutScheduled(key: String, timeout: Long) =
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = key
+                long1 = timeout
+            },
+            { "recommendation timeout scheduled for $str1 in $long1 ms" }
         )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 93be6a7..4827a16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -162,8 +162,8 @@
                     context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
                         UI_MODE_NIGHT_YES
                 )
-                    colorScheme.accent1[2]
-                else colorScheme.accent1[3]
+                    colorScheme.accent1.s100
+                else colorScheme.accent1.s200
             },
             { seamlessColor: Int ->
                 val accentColorList = ColorStateList.valueOf(seamlessColor)
@@ -230,7 +230,14 @@
 
     fun updateColorScheme(colorScheme: ColorScheme?): Boolean {
         var anyChanged = false
-        colorTransitions.forEach { anyChanged = it.updateColorScheme(colorScheme) || anyChanged }
+        colorTransitions.forEach {
+            val isChanged = it.updateColorScheme(colorScheme)
+
+            // Ignore changes to colorSeamless, since that is expected when toggling dark mode
+            if (it == colorSeamless) return@forEach
+
+            anyChanged = isChanged || anyChanged
+        }
         colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
         return anyChanged
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index e7f7647..fac1d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.ui.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.media.controls.util.SmallHash
 import com.android.systemui.plugins.ActivityStarter
@@ -88,7 +89,8 @@
     falsingManager: FalsingManager,
     dumpManager: DumpManager,
     private val logger: MediaUiEventLogger,
-    private val debugLogger: MediaCarouselControllerLogger
+    private val debugLogger: MediaCarouselControllerLogger,
+    private val mediaFlags: MediaFlags,
 ) : Dumpable {
     /** The current width of the carousel */
     private var currentCarouselWidth: Int = 0
@@ -366,7 +368,7 @@
                     data: SmartspaceMediaData,
                     shouldPrioritize: Boolean
                 ) {
-                    debugLogger.logRecommendationLoaded(key)
+                    debugLogger.logRecommendationLoaded(key, data.isActive)
                     // Log the case where the hidden media carousel with the existed inactive resume
                     // media is shown by the Smartspace signal.
                     if (data.isActive) {
@@ -440,7 +442,12 @@
                             logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded)
                         }
                     } else {
-                        onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
+                        if (!mediaFlags.isPersistentSsCardEnabled()) {
+                            // Handle update to inactive as a removal
+                            onSmartspaceMediaDataRemoved(data.targetId, immediately = true)
+                        } else {
+                            addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
+                        }
                     }
                 }
 
@@ -631,7 +638,19 @@
     ) =
         traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") {
             if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
-            if (MediaPlayerData.getMediaPlayer(key) != null) {
+            MediaPlayerData.getMediaPlayer(key)?.let {
+                if (mediaFlags.isPersistentSsCardEnabled()) {
+                    // The card exists, but could have changed active state, so update for sorting
+                    MediaPlayerData.addMediaRecommendation(
+                        key,
+                        data,
+                        it,
+                        shouldPrioritize,
+                        systemClock,
+                        debugLogger,
+                        update = true,
+                    )
+                }
                 Log.w(TAG, "Skip adding smartspace target in carousel")
                 return
             }
@@ -647,7 +666,11 @@
 
             val newRecs = mediaControlPanelFactory.get()
             newRecs.attachRecommendation(
-                RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
+                RecommendationViewHolder.create(
+                    LayoutInflater.from(context),
+                    mediaContent,
+                    mediaFlags.isRecommendationCardUpdateEnabled()
+                )
             )
             newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
             val lp =
@@ -666,7 +689,7 @@
                 newRecs,
                 shouldPrioritize,
                 systemClock,
-                debugLogger
+                debugLogger,
             )
             updatePlayerToState(newRecs, noAnimation = true)
             reorderAllPlayers(curVisibleMediaKey)
@@ -1219,17 +1242,18 @@
         player: MediaControlPanel,
         shouldPrioritize: Boolean,
         clock: SystemClock,
-        debugLogger: MediaCarouselControllerLogger? = null
+        debugLogger: MediaCarouselControllerLogger? = null,
+        update: Boolean = false
     ) {
         shouldPrioritizeSs = shouldPrioritize
         val removedPlayer = removeMediaPlayer(key)
-        if (removedPlayer != null && removedPlayer != player) {
+        if (!update && removedPlayer != null && removedPlayer != player) {
             debugLogger?.logPotentialMemoryLeak(key)
         }
         val sortKey =
             MediaSortKey(
                 isSsMediaRec = true,
-                EMPTY.copy(isPlaying = false),
+                EMPTY.copy(active = data.isActive, isPlaying = false),
                 key,
                 clock.currentTimeMillis(),
                 isSsReactivated = true
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
index eed1bd7..35bda15 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
@@ -48,8 +48,16 @@
     fun logMediaRemoved(key: String) =
         buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
 
-    fun logRecommendationLoaded(key: String) =
-        buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add recommendation $str1" })
+    fun logRecommendationLoaded(key: String, isActive: Boolean) =
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = key
+                bool1 = isActive
+            },
+            { "add recommendation $str1, active $bool1" }
+        )
 
     fun logRecommendationRemoved(key: String, immediately: Boolean) =
         buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
index 82abf9b..2a8362b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
@@ -19,28 +19,28 @@
 import com.android.systemui.monet.ColorScheme
 
 /** Returns the surface color for media controls based on the scheme. */
-internal fun surfaceFromScheme(scheme: ColorScheme) = scheme.accent2[9] // A2-800
+internal fun surfaceFromScheme(scheme: ColorScheme) = scheme.accent2.s800 // A2-800
 
 /** Returns the primary accent color for media controls based on the scheme. */
-internal fun accentPrimaryFromScheme(scheme: ColorScheme) = scheme.accent1[2] // A1-100
+internal fun accentPrimaryFromScheme(scheme: ColorScheme) = scheme.accent1.s100 // A1-100
 
 /** Returns the secondary accent color for media controls based on the scheme. */
-internal fun accentSecondaryFromScheme(scheme: ColorScheme) = scheme.accent1[3] // A1-200
+internal fun accentSecondaryFromScheme(scheme: ColorScheme) = scheme.accent1.s200 // A1-200
 
 /** Returns the primary text color for media controls based on the scheme. */
-internal fun textPrimaryFromScheme(scheme: ColorScheme) = scheme.neutral1[1] // N1-50
+internal fun textPrimaryFromScheme(scheme: ColorScheme) = scheme.neutral1.s50 // N1-50
 
 /** Returns the inverse of the primary text color for media controls based on the scheme. */
-internal fun textPrimaryInverseFromScheme(scheme: ColorScheme) = scheme.neutral1[10] // N1-900
+internal fun textPrimaryInverseFromScheme(scheme: ColorScheme) = scheme.neutral1.s900 // N1-900
 
 /** Returns the secondary text color for media controls based on the scheme. */
-internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2[3] // N2-200
+internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2.s200 // N2-200
 
 /** Returns the tertiary text color for media controls based on the scheme. */
-internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2[5] // N2-400
+internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2.s400 // N2-400
 
 /** Returns the color for the start of the background gradient based on the scheme. */
-internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2[8] // A2-700
+internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2.s700 // A2-700
 
 /** Returns the color for the end of the background gradient based on the scheme. */
-internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1[8] // A1-700
+internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1.s700 // A1-700
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 45d50f0..0b4b668a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -56,11 +56,13 @@
 import android.view.animation.Interpolator;
 import android.widget.ImageButton;
 import android.widget.ImageView;
+import android.widget.SeekBar;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
+import androidx.appcompat.content.res.AppCompatResources;
 import androidx.constraintlayout.widget.ConstraintSet;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -114,8 +116,6 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
-import dagger.Lazy;
-
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
@@ -123,6 +123,7 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
 import kotlin.Triple;
 import kotlin.Unit;
 
@@ -522,8 +523,13 @@
         }
 
         // Seek Bar
-        final MediaController controller = getController();
-        mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+        if (data.getResumption() && data.getResumeProgress() != null) {
+            double progress = data.getResumeProgress();
+            mSeekBarViewModel.updateStaticProgress(progress);
+        } else {
+            final MediaController controller = getController();
+            mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+        }
 
         // Show the broadcast dialog button only when the le audio is enabled.
         mShowBroadcastDialogButton =
@@ -728,9 +734,14 @@
             contentDescription =
                     mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
         } else if (data != null) {
-            contentDescription = mContext.getString(
-                    R.string.controls_media_smartspace_rec_description,
-                    data.getAppName(mContext));
+            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+                contentDescription = mContext.getString(
+                        R.string.controls_media_smartspace_rec_header);
+            } else {
+                contentDescription = mContext.getString(
+                        R.string.controls_media_smartspace_rec_description,
+                        data.getAppName(mContext));
+            }
         } else {
             contentDescription = null;
         }
@@ -752,43 +763,16 @@
         int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
         int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
 
-        // WallpaperColors.fromBitmap takes a good amount of time. We do that work
-        // on the background executor to avoid stalling animations on the UI Thread.
         mBackgroundExecutor.execute(() -> {
             // Album art
             ColorScheme mutableColorScheme = null;
             Drawable artwork;
             boolean isArtworkBound;
             Icon artworkIcon = data.getArtwork();
-            WallpaperColors wallpaperColors = null;
-            if (artworkIcon != null) {
-                if (artworkIcon.getType() == Icon.TYPE_BITMAP
-                        || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
-                    // Avoids extra processing if this is already a valid bitmap
-                    wallpaperColors = WallpaperColors
-                            .fromBitmap(artworkIcon.getBitmap());
-                } else {
-                    Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
-                    if (artworkDrawable != null) {
-                        wallpaperColors = WallpaperColors
-                                .fromDrawable(artworkIcon.loadDrawable(mContext));
-                    }
-                }
-            }
+            WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
             if (wallpaperColors != null) {
                 mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
-                Drawable albumArt = getScaledBackground(artworkIcon, width, height);
-                GradientDrawable gradient = (GradientDrawable) mContext
-                        .getDrawable(R.drawable.qs_media_scrim);
-                gradient.setColors(new int[] {
-                        ColorUtilKt.getColorWithAlpha(
-                                MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
-                                0.25f),
-                        ColorUtilKt.getColorWithAlpha(
-                                MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
-                                0.9f),
-                });
-                artwork = new LayerDrawable(new Drawable[] { albumArt, gradient });
+                artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
                 isArtworkBound = true;
             } else {
                 // If there's no artwork, use colors from the app icon
@@ -867,6 +851,96 @@
         });
     }
 
+    private void bindRecommendationArtwork(
+            SmartspaceAction recommendation,
+            String packageName,
+            int itemIndex
+    ) {
+        final int traceCookie = recommendation.hashCode();
+        final String traceName =
+                "MediaControlPanel#bindRecommendationArtwork<" + packageName + ">";
+        Trace.beginAsyncSection(traceName, traceCookie);
+
+        // Capture width & height from views in foreground for artwork scaling in background
+        int width = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredWidth();
+        int height = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredHeight();
+
+        mBackgroundExecutor.execute(() -> {
+            // Album art
+            ColorScheme mutableColorScheme = null;
+            Drawable artwork;
+            Icon artworkIcon = recommendation.getIcon();
+            WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
+            if (wallpaperColors != null) {
+                mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
+                artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
+            } else {
+                artwork = new ColorDrawable(Color.TRANSPARENT);
+            }
+
+            mMainExecutor.execute(() -> {
+                // Bind the artwork drawable to media cover.
+                ImageView mediaCover =
+                        mRecommendationViewHolder.getMediaCoverItems().get(itemIndex);
+                mediaCover.setImageDrawable(artwork);
+
+                // Set up the app icon.
+                ImageView appIconView = mRecommendationViewHolder.getMediaAppIcons().get(itemIndex);
+                appIconView.clearColorFilter();
+                try {
+                    Drawable icon = mContext.getPackageManager()
+                            .getApplicationIcon(packageName);
+                    appIconView.setImageDrawable(icon);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.w(TAG, "Cannot find icon for package " + packageName, e);
+                    appIconView.setImageResource(R.drawable.ic_music_note);
+                }
+                Trace.endAsyncSection(traceName, traceCookie);
+            });
+        });
+    }
+
+    // This method should be called from a background thread. WallpaperColors.fromBitmap takes a
+    // good amount of time. We do that work on the background executor to avoid stalling animations
+    // on the UI Thread.
+    private WallpaperColors getWallpaperColor(Icon artworkIcon) {
+        if (artworkIcon != null) {
+            if (artworkIcon.getType() == Icon.TYPE_BITMAP
+                    || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+                // Avoids extra processing if this is already a valid bitmap
+                return WallpaperColors
+                        .fromBitmap(artworkIcon.getBitmap());
+            } else {
+                Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
+                if (artworkDrawable != null) {
+                    return WallpaperColors
+                            .fromDrawable(artworkIcon.loadDrawable(mContext));
+                }
+            }
+        }
+        return null;
+    }
+
+    private LayerDrawable addGradientToIcon(
+            Icon artworkIcon,
+            ColorScheme mutableColorScheme,
+            int width,
+            int height
+    ) {
+        Drawable albumArt = getScaledBackground(artworkIcon, width, height);
+        GradientDrawable gradient = (GradientDrawable) AppCompatResources
+                .getDrawable(mContext, R.drawable.qs_media_scrim);
+        gradient.setColors(new int[] {
+                ColorUtilKt.getColorWithAlpha(
+                        MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
+                        0.25f),
+                ColorUtilKt.getColorWithAlpha(
+                        MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
+                        0.9f),
+        });
+        return new LayerDrawable(new Drawable[] { albumArt, gradient });
+    }
+
     private void scaleTransitionDrawableLayer(TransitionDrawable transitionDrawable, int layer,
             int targetWidth, int targetHeight) {
         Drawable drawable = transitionDrawable.getDrawable(layer);
@@ -1224,8 +1298,10 @@
         PackageManager packageManager = mContext.getPackageManager();
         // Set up media source app's logo.
         Drawable icon = packageManager.getApplicationIcon(applicationInfo);
-        ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
-        headerLogoImageView.setImageDrawable(icon);
+        if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+            ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
+            headerLogoImageView.setImageDrawable(icon);
+        }
         fetchAndUpdateRecommendationColors(icon);
 
         // Set up media rec card's tap action if applicable.
@@ -1245,7 +1321,15 @@
 
             // Set up media item cover.
             ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
-            mediaCoverImageView.setImageIcon(recommendation.getIcon());
+            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+                bindRecommendationArtwork(
+                        recommendation,
+                        data.getPackageName(),
+                        itemIndex
+                );
+            } else {
+                mediaCoverImageView.setImageIcon(recommendation.getIcon());
+            }
 
             // Set up the media item's click listener if applicable.
             ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
@@ -1275,7 +1359,6 @@
                                 recommendation.getTitle(), artistName, appName));
             }
 
-
             // Set up title
             CharSequence title = recommendation.getTitle();
             hasTitle |= !TextUtils.isEmpty(title);
@@ -1289,6 +1372,24 @@
             hasSubtitle |= !TextUtils.isEmpty(subtitle);
             TextView subtitleView = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
             subtitleView.setText(subtitle);
+
+            // Set up progress bar
+            if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+                SeekBar mediaProgressBar =
+                        mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
+                TextView mediaSubtitle =
+                        mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
+                // show progress bar if the recommended album is played.
+                Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
+                if (progress == null || progress <= 0.0) {
+                    mediaProgressBar.setVisibility(View.GONE);
+                    mediaSubtitle.setVisibility(View.VISIBLE);
+                } else {
+                    mediaProgressBar.setProgress((int) (progress * 100));
+                    mediaProgressBar.setVisibility(View.VISIBLE);
+                    mediaSubtitle.setVisibility(View.GONE);
+                }
+            }
         }
         mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
 
@@ -1353,12 +1454,22 @@
         int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
         int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
 
+        if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+            mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
+        }
+
         mRecommendationViewHolder.getRecommendations()
                 .setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
         mRecommendationViewHolder.getMediaTitles().forEach(
                 (title) -> title.setTextColor(textPrimaryColor));
         mRecommendationViewHolder.getMediaSubtitles().forEach(
                 (subtitle) -> subtitle.setTextColor(textSecondaryColor));
+        if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
+            mRecommendationViewHolder.getMediaProgressBars().forEach(
+                    (progressBar) -> progressBar.setProgressTintList(
+                            ColorStateList.valueOf(textPrimaryColor))
+            );
+        }
 
         mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 2ec7be6..1e6002c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.media.controls.models.player.MediaViewHolder
 import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
 import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.animation.MeasurementOutput
 import com.android.systemui.util.animation.TransitionLayout
@@ -45,7 +46,8 @@
     private val context: Context,
     private val configurationController: ConfigurationController,
     private val mediaHostStatesManager: MediaHostStatesManager,
-    private val logger: MediaViewLogger
+    private val logger: MediaViewLogger,
+    private val mediaFlags: MediaFlags,
 ) {
 
     /**
@@ -646,8 +648,13 @@
                 expandedLayout.load(context, R.xml.media_session_expanded)
             }
             TYPE.RECOMMENDATION -> {
-                collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
-                expandedLayout.load(context, R.xml.media_recommendation_expanded)
+                if (mediaFlags.isRecommendationCardUpdateEnabled()) {
+                    collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
+                    expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
+                } else {
+                    collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
+                    expandedLayout.load(context, R.xml.media_recommendation_expanded)
+                }
             }
         }
         refreshState()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index bcfceaa..85282a1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -19,8 +19,12 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Bundle;
 import android.text.TextUtils;
 
+import androidx.core.math.MathUtils;
+import androidx.media.utils.MediaConstants;
+
 /**
  * Utility class with common methods for media controls
  */
@@ -50,4 +54,35 @@
                         : unknownName);
         return applicationName;
     }
+
+    /**
+     * Check the bundle for extras indicating the progress percentage
+     *
+     * @param extras
+     * @return the progress value between 0-1 inclusive if prsent, otherwise null
+     */
+    public static Double getDescriptionProgress(Bundle extras) {
+        if (!extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
+            return null;
+        }
+
+        int status = extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS);
+        switch (status) {
+            case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED:
+                return 0.0;
+            case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED:
+                return 1.0;
+            case MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED: {
+                if (extras
+                        .containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE)) {
+                    double percent = extras
+                            .getDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE);
+                    return MathUtils.clamp(percent, 0.0, 1.0);
+                } else {
+                    return 0.5;
+                }
+            }
+        }
+        return null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 5bc35ca..c3fa76e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -45,4 +45,20 @@
 
     /** Check whether we show explicit indicator on UMO */
     fun isExplicitIndicatorEnabled() = featureFlags.isEnabled(Flags.MEDIA_EXPLICIT_INDICATOR)
+
+    /**
+     * If true, keep active media controls for the lifetime of the MediaSession, regardless of
+     * whether the underlying notification was dismissed
+     */
+    fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
+
+    /** Check whether we show the updated recommendation card. */
+    fun isRecommendationCardUpdateEnabled() =
+        featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
+
+    /** Check whether to get progress information for resume players */
+    fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
+
+    /** If true, do not automatically dismiss the recommendation card */
+    fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index bb833df..9ae4577 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -17,8 +17,7 @@
 package com.android.systemui.media.dagger;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.log.dagger.MediaTttReceiverLogBuffer;
-import com.android.systemui.log.dagger.MediaTttSenderLogBuffer;
+import com.android.systemui.log.LogBufferFactory;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.media.controls.ui.MediaHost;
@@ -29,12 +28,9 @@
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
-import com.android.systemui.media.taptotransfer.receiver.ChipReceiverInfo;
-import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
+import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogBuffer;
+import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogBuffer;
 import com.android.systemui.plugins.log.LogBuffer;
-import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo;
 
 import java.util.Optional;
 
@@ -94,22 +90,22 @@
         return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
     }
 
+    /** Provides a logging buffer related to the media tap-to-transfer chip on the sender device. */
     @Provides
     @SysUISingleton
-    @MediaTttSenderLogger
-    static MediaTttLogger<ChipbarInfo> providesMediaTttSenderLogger(
-            @MediaTttSenderLogBuffer LogBuffer buffer
-    ) {
-        return new MediaTttLogger<>("Sender", buffer);
+    @MediaTttSenderLogBuffer
+    static LogBuffer provideMediaTttSenderLogBuffer(LogBufferFactory factory) {
+        return factory.create("MediaTttSender", 30);
     }
 
+    /**
+     * Provides a logging buffer related to the media tap-to-transfer chip on the receiver device.
+     */
     @Provides
     @SysUISingleton
-    @MediaTttReceiverLogger
-    static MediaTttLogger<ChipReceiverInfo> providesMediaTttReceiverLogger(
-            @MediaTttReceiverLogBuffer LogBuffer buffer
-    ) {
-        return new MediaTttLogger<>("Receiver", buffer);
+    @MediaTttReceiverLogBuffer
+    static LogBuffer provideMediaTttReceiverLogBuffer(LogBufferFactory factory) {
+        return factory.create("MediaTttReceiver", 20);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 51b5a3d..769e0c8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,17 +16,21 @@
 
 package com.android.systemui.media.dialog;
 
+import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CheckBox;
 import android.widget.TextView;
 
+import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
 import androidx.core.widget.CompoundButtonCompat;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -186,6 +190,17 @@
                     mCurrentActivePosition = position;
                     updateFullItemClickListener(v -> onItemClick(v, device));
                     setSingleLineLayout(getItemTitle(device));
+                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+                        && mController.isSubStatusSupported() && device.hasDisabledReason()) {
+                    //update to subtext with device status
+                    setUpDeviceIcon(device);
+                    mSubTitleText.setText(
+                            Api34Impl.composeDisabledReason(device.getDisableReason(), mContext));
+                    updateConnectionFailedStatusIcon();
+                    updateFullItemClickListener(null);
+                    setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
+                            false /* showProgressBar */, true /* showSubtitle */,
+                            true /* showStatus */);
                 } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
                     setUpDeviceIcon(device);
                     updateConnectionFailedStatusIcon();
@@ -389,4 +404,12 @@
             mTitleText.setText(groupDividerTitle);
         }
     }
+
+    @RequiresApi(34)
+    private static class Api34Impl {
+        @DoNotInline
+        static String composeDisabledReason(int reason, Context context) {
+            return "";
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 4e08050..dc75538 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -45,6 +45,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.settingslib.Utils;
@@ -142,11 +143,12 @@
         final TextView mVolumeValueText;
         final ImageView mTitleIcon;
         final ProgressBar mProgressBar;
-        final MediaOutputSeekbar mSeekBar;
         final LinearLayout mTwoLineLayout;
         final ImageView mStatusIcon;
         final CheckBox mCheckBox;
         final ViewGroup mEndTouchArea;
+        @VisibleForTesting
+        MediaOutputSeekbar mSeekBar;
         private String mDeviceId;
         private ValueAnimator mCornerAnimator;
         private ValueAnimator mVolumeAnimator;
@@ -390,6 +392,7 @@
                         mTitleIcon.setVisibility(View.VISIBLE);
                         mVolumeValueText.setVisibility(View.GONE);
                     }
+                    mController.logInteractionAdjustVolume(device);
                     mIsDragging = false;
                 }
             });
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index a9e1a4d..4803371 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -95,6 +95,7 @@
     private RecyclerView mDevicesRecyclerView;
     private LinearLayout mDeviceListLayout;
     private LinearLayout mCastAppLayout;
+    private LinearLayout mMediaMetadataSectionLayout;
     private Button mDoneButton;
     private Button mStopButton;
     private Button mAppButton;
@@ -240,6 +241,7 @@
         mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle);
         mHeaderIcon = mDialogView.requireViewById(R.id.header_icon);
         mDevicesRecyclerView = mDialogView.requireViewById(R.id.list_result);
+        mMediaMetadataSectionLayout = mDialogView.requireViewById(R.id.media_metadata_section);
         mDeviceListLayout = mDialogView.requireViewById(R.id.device_list);
         mDoneButton = mDialogView.requireViewById(R.id.done);
         mStopButton = mDialogView.requireViewById(R.id.stop);
@@ -255,21 +257,17 @@
         mDevicesRecyclerView.setLayoutManager(mLayoutManager);
         mDevicesRecyclerView.setAdapter(mAdapter);
         mDevicesRecyclerView.setHasFixedSize(false);
-        // Init header icon
-        mHeaderIcon.setOnClickListener(v -> onHeaderIconClick());
         // Init bottom buttons
         mDoneButton.setOnClickListener(v -> dismiss());
         mStopButton.setOnClickListener(v -> {
             mMediaOutputController.releaseSession();
             dismiss();
         });
-        mAppButton.setOnClickListener(v -> {
-            mBroadcastSender.closeSystemDialogs();
-            if (mMediaOutputController.getAppLaunchIntent() != null) {
-                mContext.startActivity(mMediaOutputController.getAppLaunchIntent());
-            }
-            dismiss();
-        });
+        mAppButton.setOnClickListener(v -> mMediaOutputController.tryToLaunchMediaApplication());
+        if (mMediaOutputController.isAdvancedLayoutSupported()) {
+            mMediaMetadataSectionLayout.setOnClickListener(
+                    v -> mMediaOutputController.tryToLaunchMediaApplication());
+        }
     }
 
     @Override
@@ -560,7 +558,7 @@
 
     @Override
     public void dismissDialog() {
-        dismiss();
+        mBroadcastSender.closeSystemDialogs();
     }
 
     void onHeaderIconClick() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 7bc0c0c..5f5c686 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -141,7 +141,7 @@
     @VisibleForTesting
     LocalMediaManager mLocalMediaManager;
     @VisibleForTesting
-    private MediaOutputMetricLogger mMetricLogger;
+    MediaOutputMetricLogger mMetricLogger;
     private int mCurrentState;
 
     private int mColorItemContent;
@@ -382,6 +382,15 @@
         return mContext.getPackageManager().getLaunchIntentForPackage(mPackageName);
     }
 
+    void tryToLaunchMediaApplication() {
+        Intent launchIntent = getAppLaunchIntent();
+        if (launchIntent != null) {
+            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mCallback.dismissDialog();
+            mContext.startActivity(launchIntent);
+        }
+    }
+
     CharSequence getHeaderTitle() {
         if (mMediaController != null) {
             final MediaMetadata metadata = mMediaController.getMetadata();
@@ -493,20 +502,20 @@
         ColorScheme mCurrentColorScheme = new ColorScheme(wallpaperColors,
                 isDarkTheme);
         if (isDarkTheme) {
-            mColorItemContent = mCurrentColorScheme.getAccent1().get(2); // A1-100
-            mColorSeekbarProgress = mCurrentColorScheme.getAccent2().get(7); // A2-600
-            mColorButtonBackground = mCurrentColorScheme.getAccent1().get(4); // A1-300
-            mColorItemBackground = mCurrentColorScheme.getNeutral2().get(9); // N2-800
-            mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().get(9); // A2-800
-            mColorPositiveButtonText = mCurrentColorScheme.getAccent2().get(9); // A2-800
-            mColorDialogBackground = mCurrentColorScheme.getNeutral1().get(10); // N1-900
+            mColorItemContent = mCurrentColorScheme.getAccent1().getS100(); // A1-100
+            mColorSeekbarProgress = mCurrentColorScheme.getAccent2().getS600(); // A2-600
+            mColorButtonBackground = mCurrentColorScheme.getAccent1().getS300(); // A1-300
+            mColorItemBackground = mCurrentColorScheme.getNeutral2().getS800(); // N2-800
+            mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().getS800(); // A2-800
+            mColorPositiveButtonText = mCurrentColorScheme.getAccent2().getS800(); // A2-800
+            mColorDialogBackground = mCurrentColorScheme.getNeutral1().getS900(); // N1-900
         } else {
-            mColorItemContent = mCurrentColorScheme.getAccent1().get(9); // A1-800
-            mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(4); // A1-300
-            mColorButtonBackground = mCurrentColorScheme.getAccent1().get(7); // A1-600
-            mColorItemBackground = mCurrentColorScheme.getAccent2().get(1); // A2-50
-            mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().get(2); // A1-100
-            mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().get(1); // N1-50
+            mColorItemContent = mCurrentColorScheme.getAccent1().getS800(); // A1-800
+            mColorSeekbarProgress = mCurrentColorScheme.getAccent1().getS300(); // A1-300
+            mColorButtonBackground = mCurrentColorScheme.getAccent1().getS600(); // A1-600
+            mColorItemBackground = mCurrentColorScheme.getAccent2().getS50(); // A2-50
+            mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().getS100(); // A1-100
+            mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().getS50(); // N1-50
             mColorDialogBackground = mCurrentColorScheme.getBackgroundColor();
         }
     }
@@ -748,6 +757,10 @@
         return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING);
     }
 
+    public boolean isSubStatusSupported() {
+        return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_DEVICE_STATUS);
+    }
+
     List<MediaDevice> getGroupMediaDevices() {
         final List<MediaDevice> selectedDevices = getSelectedMediaDevice();
         final List<MediaDevice> selectableDevices = getSelectableMediaDevice();
@@ -857,12 +870,15 @@
     }
 
     void adjustVolume(MediaDevice device, int volume) {
-        mMetricLogger.logInteractionAdjustVolume(device);
         ThreadUtils.postOnBackgroundThread(() -> {
             device.requestSetVolume(volume);
         });
     }
 
+    void logInteractionAdjustVolume(MediaDevice device) {
+        mMetricLogger.logInteractionAdjustVolume(device);
+    }
+
     String getPackageName() {
         return mPackageName;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
new file mode 100644
index 0000000..e35575b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -0,0 +1,71 @@
+/*
+ * 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.systemui.media.dialog;
+
+import android.annotation.MainThread;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.statusbar.CommandQueue;
+
+import javax.inject.Inject;
+
+/** Controls display of media output switcher. */
+@SysUISingleton
+public class MediaOutputSwitcherDialogUI implements CoreStartable, CommandQueue.Callbacks {
+
+    private static final String TAG = "MediaOutputSwitcherDialogUI";
+
+    private final CommandQueue mCommandQueue;
+    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+    private final FeatureFlags mFeatureFlags;
+
+    @Inject
+    public MediaOutputSwitcherDialogUI(
+            Context context,
+            CommandQueue commandQueue,
+            MediaOutputDialogFactory mediaOutputDialogFactory,
+            FeatureFlags featureFlags) {
+        mCommandQueue = commandQueue;
+        mMediaOutputDialogFactory = mediaOutputDialogFactory;
+        mFeatureFlags = featureFlags;
+    }
+
+    @Override
+    public void start() {
+        if (mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_SHOW_API_ENABLED)) {
+            mCommandQueue.addCallback(this);
+        } else {
+            Log.w(TAG, "Show media output switcher is not enabled.");
+        }
+    }
+
+    @Override
+    @MainThread
+    public void showMediaOutputSwitcher(String packageName) {
+        if (!TextUtils.isEmpty(packageName)) {
+            mMediaOutputDialogFactory.create(packageName, false, null);
+        } else {
+            Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 8a565fa..60504e4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -30,4 +30,8 @@
     /** Check whether the flag for the receiver success state is enabled. */
     fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
         featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
+
+    /** True if the media transfer chip can be dismissed via a gesture. */
+    fun isMediaTttDismissGestureEnabled(): Boolean =
+        featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
deleted file mode 100644
index 8aef938..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2022 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.systemui.media.taptotransfer.common
-
-import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
-import com.android.systemui.temporarydisplay.TemporaryViewLogger
-
-/**
- * A logger for media tap-to-transfer events.
- *
- * @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
- *
- * TODO(b/245610654): We should de-couple the sender and receiver loggers, since they're vastly
- * different experiences.
- */
-class MediaTttLogger<T : TemporaryViewInfo>(
-    deviceTypeTag: String,
-    buffer: LogBuffer
-) : TemporaryViewLogger<T>(buffer, BASE_TAG + deviceTypeTag) {
-    /** Logs a change in the chip state for the given [mediaRouteId]. */
-    fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) {
-        buffer.log(
-            tag,
-            LogLevel.DEBUG,
-            {
-                str1 = stateName
-                str2 = mediaRouteId
-                str3 = packageName
-            },
-            { "State changed to $str1 for ID=$str2 package=$str3" }
-        )
-    }
-
-    /**
-     * Logs an error in trying to update to [displayState].
-     *
-     * [displayState] is either a [android.app.StatusBarManager.MediaTransferSenderState] or
-     * a [android.app.StatusBarManager.MediaTransferReceiverState].
-     */
-    fun logStateChangeError(displayState: Int) {
-        buffer.log(
-            tag,
-            LogLevel.ERROR,
-            { int1 = displayState },
-            { "Cannot display state=$int1; aborting" }
-        )
-    }
-
-    /**
-     * Logs an invalid sender state transition error in trying to update to [desiredState].
-     *
-     * @param currentState the previous state of the chip.
-     * @param desiredState the new state of the chip.
-     */
-    fun logInvalidStateTransitionError(
-        currentState: String,
-        desiredState: String
-    ) {
-        buffer.log(
-                tag,
-                LogLevel.ERROR,
-                {
-                    str1 = currentState
-                    str2 = desiredState
-                },
-                { "Cannot display state=$str2 after state=$str1; invalid transition" }
-        )
-    }
-
-    /** Logs that we couldn't find information for [packageName]. */
-    fun logPackageNotFound(packageName: String) {
-        buffer.log(
-            tag,
-            LogLevel.DEBUG,
-            { str1 = packageName },
-            { "Package $str1 could not be found" }
-        )
-    }
-
-    /**
-     * Logs that a removal request has been bypassed (ignored).
-     *
-     * @param removalReason the reason that the chip removal was requested.
-     * @param bypassReason the reason that the request was bypassed.
-     */
-    fun logRemovalBypass(removalReason: String, bypassReason: String) {
-        buffer.log(
-            tag,
-            LogLevel.DEBUG,
-            {
-                str1 = removalReason
-                str2 = bypassReason
-            },
-            { "Chip removal requested due to $str1; however, removal was ignored because $str2" })
-    }
-}
-
-private const val BASE_TAG = "MediaTtt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
new file mode 100644
index 0000000..0e839c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.systemui.media.taptotransfer.common
+
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+
+/** A helper for logging media tap-to-transfer events. */
+object MediaTttLoggerUtils {
+    fun logStateChange(
+        buffer: LogBuffer,
+        tag: String,
+        stateName: String,
+        mediaRouteId: String,
+        packageName: String?,
+    ) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = stateName
+                str2 = mediaRouteId
+                str3 = packageName
+            },
+            { "State changed to $str1 for ID=$str2 package=$str3" }
+        )
+    }
+
+    fun logStateChangeError(buffer: LogBuffer, tag: String, displayState: Int) {
+        buffer.log(
+            tag,
+            LogLevel.ERROR,
+            { int1 = displayState },
+            { "Cannot display state=$int1; aborting" }
+        )
+    }
+
+    fun logPackageNotFound(buffer: LogBuffer, tag: String, packageName: String) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            { str1 = packageName },
+            { "Package $str1 could not be found" }
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 066c185..a3ae943 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.TintedIcon
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
 
 /** Utility methods for media tap-to-transfer. */
 class MediaTttUtils {
@@ -43,12 +42,13 @@
          * default name and icon if we can't find the app name/icon.
          *
          * @param appPackageName the package name of the app playing the media.
-         * @param logger the logger to use for any errors.
+         * @param onPackageNotFoundException a function run if a
+         * [PackageManager.NameNotFoundException] occurs.
          */
         fun getIconInfoFromPackageName(
             context: Context,
             appPackageName: String?,
-            logger: MediaTttLogger<out TemporaryViewInfo>
+            onPackageNotFoundException: () -> Unit,
         ): IconInfo {
             if (appPackageName != null) {
                 val packageManager = context.packageManager
@@ -70,7 +70,7 @@
                         isAppIcon = true
                     )
                 } catch (e: PackageManager.NameNotFoundException) {
-                    logger.logPackageNotFound(appPackageName)
+                    onPackageNotFoundException.invoke()
                 }
             }
             return IconInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 889147b..34bf74fa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -30,8 +30,8 @@
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
+import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
 import com.android.internal.widget.CachingIconView
-import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.ui.binder.TintedIconViewBinder
@@ -40,7 +40,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttIcon
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -65,7 +64,7 @@
 open class MediaTttChipControllerReceiver @Inject constructor(
         private val commandQueue: CommandQueue,
         context: Context,
-        @MediaTttReceiverLogger logger: MediaTttLogger<ChipReceiverInfo>,
+        logger: MediaTttReceiverLogger,
         windowManager: WindowManager,
         mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
@@ -78,7 +77,8 @@
         private val viewUtil: ViewUtil,
         wakeLockBuilder: WakeLock.Builder,
         systemClock: SystemClock,
-) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>(
+        private val rippleController: MediaTttReceiverRippleController,
+) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttReceiverLogger>(
         context,
         logger,
         windowManager,
@@ -114,9 +114,6 @@
         }
     }
 
-    private var maxRippleWidth: Float = 0f
-    private var maxRippleHeight: Float = 0f
-
     private fun updateMediaTapToTransferReceiverDisplay(
         @StatusBarManager.MediaTransferReceiverState displayState: Int,
         routeInfo: MediaRoute2Info,
@@ -175,9 +172,10 @@
     }
 
     override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
-        var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
-            context, newInfo.routeInfo.clientPackageName, logger
-        )
+        val packageName = newInfo.routeInfo.clientPackageName
+        var iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, packageName) {
+            logger.logPackageNotFound(packageName)
+        }
 
         if (newInfo.appNameOverride != null) {
             iconInfo = iconInfo.copy(
@@ -201,41 +199,44 @@
 
         val iconView = currentView.getAppIconView()
         iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
+        iconView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
         TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView)
     }
 
     override fun animateViewIn(view: ViewGroup) {
         val appIconView = view.getAppIconView()
-        appIconView.animate()
-                .translationYBy(-1 * getTranslationAmount().toFloat())
-                .setDuration(ICON_TRANSLATION_ANIM_DURATION)
-                .start()
-        appIconView.animate()
-                .alpha(1f)
-                .setDuration(ICON_ALPHA_ANIM_DURATION)
-                .start()
-        // Using withEndAction{} doesn't apply a11y focus when screen is unlocked.
-        appIconView.postOnAnimation { view.requestAccessibilityFocus() }
-        expandRipple(view.requireViewById(R.id.ripple))
+        val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
+        val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
+        animateViewTranslationAndFade(appIconView, -1 * getTranslationAmount(), 1f)
+        animateViewTranslationAndFade(iconRippleView, -1 * getTranslationAmount(), 1f)
+        rippleController.expandToInProgressState(rippleView, iconRippleView)
     }
 
     override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
         val appIconView = view.getAppIconView()
-        appIconView.animate()
-                .translationYBy(getTranslationAmount().toFloat())
-                .setDuration(ICON_TRANSLATION_ANIM_DURATION)
-                .start()
-        appIconView.animate()
-                .alpha(0f)
-                .setDuration(ICON_ALPHA_ANIM_DURATION)
-                .start()
-
+        val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
         val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
         if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
                 mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
-            expandRippleToFull(rippleView, onAnimationEnd)
+            rippleController.expandToSuccessState(rippleView, onAnimationEnd)
+            animateViewTranslationAndFade(
+                iconRippleView,
+                -1 * getTranslationAmount(),
+                0f,
+                translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
+                alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
+            )
+            animateViewTranslationAndFade(
+                appIconView,
+                -1 * getTranslationAmount(),
+                0f,
+                translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
+                alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
+            )
         } else {
-            rippleView.collapseRipple(onAnimationEnd)
+            rippleController.collapseRipple(rippleView, onAnimationEnd)
+            animateViewTranslationAndFade(iconRippleView, getTranslationAmount(), 0f)
+            animateViewTranslationAndFade(appIconView, getTranslationAmount(), 0f)
         }
     }
 
@@ -245,74 +246,41 @@
         viewUtil.setRectToViewWindowLocation(view.getAppIconView(), outRect)
     }
 
+    /** Animation of view translation and fading. */
+    private fun animateViewTranslationAndFade(
+        view: View,
+        translationYBy: Float,
+        alphaEndValue: Float,
+        translationDuration: Long = ICON_TRANSLATION_ANIM_DURATION,
+        alphaDuration: Long = ICON_ALPHA_ANIM_DURATION,
+    ) {
+        view.animate()
+            .translationYBy(translationYBy)
+            .setDuration(translationDuration)
+            .start()
+        view.animate()
+            .alpha(alphaEndValue)
+            .setDuration(alphaDuration)
+            .start()
+    }
+
     /** Returns the amount that the chip will be translated by in its intro animation. */
-    private fun getTranslationAmount(): Int {
-        return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
-    }
-
-    private fun expandRipple(rippleView: ReceiverChipRippleView) {
-        if (rippleView.rippleInProgress()) {
-            // Skip if ripple is still playing
-            return
-        }
-
-        // In case the device orientation changes, we need to reset the layout.
-        rippleView.addOnLayoutChangeListener (
-            View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
-                if (v == null) return@OnLayoutChangeListener
-
-                val layoutChangedRippleView = v as ReceiverChipRippleView
-                layoutRipple(layoutChangedRippleView)
-                layoutChangedRippleView.invalidate()
-            }
-        )
-        rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
-            override fun onViewDetachedFromWindow(view: View?) {}
-
-            override fun onViewAttachedToWindow(view: View?) {
-                if (view == null) {
-                    return
-                }
-                val attachedRippleView = view as ReceiverChipRippleView
-                layoutRipple(attachedRippleView)
-                attachedRippleView.expandRipple()
-                attachedRippleView.removeOnAttachStateChangeListener(this)
-            }
-        })
-    }
-
-    private fun layoutRipple(rippleView: ReceiverChipRippleView, isFullScreen: Boolean = false) {
-        val windowBounds = windowManager.currentWindowMetrics.bounds
-        val height = windowBounds.height().toFloat()
-        val width = windowBounds.width().toFloat()
-
-        if (isFullScreen) {
-            maxRippleHeight = height * 2f
-            maxRippleWidth = width * 2f
-        } else {
-            maxRippleHeight = height / 2f
-            maxRippleWidth = width / 2f
-        }
-        rippleView.setMaxSize(maxRippleWidth, maxRippleHeight)
-        // Center the ripple on the bottom of the screen in the middle.
-        rippleView.setCenter(width * 0.5f, height)
-        val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
-        rippleView.setColor(color, 70)
+    private fun getTranslationAmount(): Float {
+        return rippleController.getRippleSize() * 0.5f -
+            rippleController.getReceiverIconSize()
     }
 
     private fun View.getAppIconView(): CachingIconView {
         return this.requireViewById(R.id.app_icon)
     }
 
-    private fun expandRippleToFull(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable?) {
-        layoutRipple(rippleView, true)
-        rippleView.expandToFull(maxRippleHeight, onAnimationEnd)
+    companion object {
+        private const val ICON_TRANSLATION_ANIM_DURATION = 500L
+        private const val ICON_TRANSLATION_SUCCEEDED_DURATION = 167L
+        private val ICON_ALPHA_ANIM_DURATION = 5.frames
     }
 }
 
-val ICON_TRANSLATION_ANIM_DURATION = 30.frames
-val ICON_ALPHA_ANIM_DURATION = 5.frames
-
 data class ChipReceiverInfo(
     val routeInfo: MediaRoute2Info,
     val appIconDrawableOverride: Drawable?,
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
rename to packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
index 1570d43..67e464c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttReceiverLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogBuffer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log.dagger;
+package com.android.systemui.media.taptotransfer.receiver;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -26,8 +26,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for
- * {@link com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger}.
+ * A {@link LogBuffer} for receiver logs.
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
index 54fc48d..b0c6257 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLogger.kt
@@ -13,14 +13,44 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.systemui.media.taptotransfer.receiver
 
-import java.lang.annotation.Documented
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
-import javax.inject.Qualifier
+import android.app.StatusBarManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.temporarydisplay.TemporaryViewLogger
+import javax.inject.Inject
 
-@Qualifier
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-annotation class MediaTttReceiverLogger
+/** A logger for all events related to the media tap-to-transfer receiver experience. */
+@SysUISingleton
+class MediaTttReceiverLogger
+@Inject
+constructor(
+    @MediaTttReceiverLogBuffer buffer: LogBuffer,
+) : TemporaryViewLogger<ChipReceiverInfo>(buffer, TAG) {
+
+    /** Logs a change in the chip state for the given [mediaRouteId]. */
+    fun logStateChange(
+        stateName: String,
+        mediaRouteId: String,
+        packageName: String?,
+    ) {
+        MediaTttLoggerUtils.logStateChange(buffer, TAG, stateName, mediaRouteId, packageName)
+    }
+
+    /** Logs an error in trying to update to [displayState]. */
+    fun logStateChangeError(@StatusBarManager.MediaTransferReceiverState displayState: Int) {
+        MediaTttLoggerUtils.logStateChangeError(buffer, TAG, displayState)
+    }
+
+    /** Logs that we couldn't find information for [packageName]. */
+    fun logPackageNotFound(packageName: String) {
+        MediaTttLoggerUtils.logPackageNotFound(buffer, TAG, packageName)
+    }
+
+    companion object {
+        private const val TAG = "MediaTttReceiver"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
new file mode 100644
index 0000000..5013802
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.systemui.media.taptotransfer.receiver
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.view.View
+import android.view.WindowManager
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import javax.inject.Inject
+
+/**
+ * A controller responsible for the animation of the ripples shown in media tap-to-transfer on the
+ * receiving device.
+ */
+class MediaTttReceiverRippleController
+@Inject
+constructor(
+    private val context: Context,
+    private val windowManager: WindowManager,
+) {
+
+    private var maxRippleWidth: Float = 0f
+    private var maxRippleHeight: Float = 0f
+
+    /** Expands the icon and main ripple to in-progress state */
+    fun expandToInProgressState(
+        mainRippleView: ReceiverChipRippleView,
+        iconRippleView: ReceiverChipRippleView,
+    ) {
+        expandRipple(mainRippleView, isIconRipple = false)
+        expandRipple(iconRippleView, isIconRipple = true)
+    }
+
+    private fun expandRipple(rippleView: ReceiverChipRippleView, isIconRipple: Boolean) {
+        if (rippleView.rippleInProgress()) {
+            // Skip if ripple is still playing
+            return
+        }
+
+        // In case the device orientation changes, we need to reset the layout.
+        rippleView.addOnLayoutChangeListener(
+            View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
+                if (v == null) return@OnLayoutChangeListener
+
+                val layoutChangedRippleView = v as ReceiverChipRippleView
+                if (isIconRipple) {
+                    layoutIconRipple(layoutChangedRippleView)
+                } else {
+                    layoutRipple(layoutChangedRippleView)
+                }
+                layoutChangedRippleView.invalidate()
+            }
+        )
+        rippleView.addOnAttachStateChangeListener(
+            object : View.OnAttachStateChangeListener {
+                override fun onViewDetachedFromWindow(view: View?) {}
+
+                override fun onViewAttachedToWindow(view: View?) {
+                    if (view == null) {
+                        return
+                    }
+                    val attachedRippleView = view as ReceiverChipRippleView
+                    if (isIconRipple) {
+                        layoutIconRipple(attachedRippleView)
+                    } else {
+                        layoutRipple(attachedRippleView)
+                    }
+                    attachedRippleView.expandRipple()
+                    attachedRippleView.removeOnAttachStateChangeListener(this)
+                }
+            }
+        )
+    }
+
+    /** Expands the ripple to cover the screen. */
+    fun expandToSuccessState(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable?) {
+        layoutRipple(rippleView, isFullScreen = true)
+        rippleView.expandToFull(maxRippleHeight, onAnimationEnd)
+    }
+
+    /** Collapses the ripple. */
+    fun collapseRipple(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable? = null) {
+        rippleView.collapseRipple(onAnimationEnd)
+    }
+
+    private fun layoutRipple(rippleView: ReceiverChipRippleView, isFullScreen: Boolean = false) {
+        val windowBounds = windowManager.currentWindowMetrics.bounds
+        val height = windowBounds.height().toFloat()
+        val width = windowBounds.width().toFloat()
+
+        if (isFullScreen) {
+            maxRippleHeight = height * 2f
+            maxRippleWidth = width * 2f
+        } else {
+            maxRippleHeight = getRippleSize()
+            maxRippleWidth = getRippleSize()
+        }
+        rippleView.setMaxSize(maxRippleWidth, maxRippleHeight)
+        // Center the ripple on the bottom of the screen in the middle.
+        rippleView.setCenter(width * 0.5f, height)
+        rippleView.setColor(getRippleColor(), RIPPLE_OPACITY)
+    }
+
+    private fun layoutIconRipple(iconRippleView: ReceiverChipRippleView) {
+        val windowBounds = windowManager.currentWindowMetrics.bounds
+        val height = windowBounds.height().toFloat()
+        val width = windowBounds.width().toFloat()
+        val radius = getReceiverIconSize().toFloat()
+
+        iconRippleView.setMaxSize(radius * 0.8f, radius * 0.8f)
+        iconRippleView.setCenter(
+            width * 0.5f,
+            height - getReceiverIconSize() * 0.5f - getReceiverIconBottomMargin()
+        )
+        iconRippleView.setColor(getRippleColor(), RIPPLE_OPACITY)
+    }
+
+    private fun getRippleColor(): Int {
+        var colorStateList =
+            ColorStateList.valueOf(
+                Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+            )
+        return colorStateList.withLStar(TONE_PERCENT).defaultColor
+    }
+
+    /** Returns the size of the ripple. */
+    internal fun getRippleSize(): Float {
+        return getReceiverIconSize() * 4f
+    }
+
+    /** Returns the size of the icon of the receiver. */
+    internal fun getReceiverIconSize(): Int {
+        return context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver)
+    }
+
+    /** Return the bottom margin of the icon of the receiver. */
+    internal fun getReceiverIconBottomMargin(): Int {
+        // Adding a margin to make sure ripple behind the icon is not cut by the screen bounds.
+        return context.resources.getDimensionPixelSize(
+            R.dimen.media_ttt_receiver_icon_bottom_margin
+        )
+    }
+
+    companion object {
+        const val RIPPLE_OPACITY = 70
+        const val TONE_PERCENT = 95f
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 87b2528..997370b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -33,14 +33,14 @@
     private var isStarted: Boolean
 
     init {
-        setupShader(RippleShader.RippleShape.ELLIPSE)
-        setRippleFill(true)
+        setupShader(RippleShader.RippleShape.CIRCLE)
+        setupRippleFadeParams()
         setSparkleStrength(0f)
-        duration = 3000L
         isStarted = false
     }
 
     fun expandRipple(onAnimationEnd: Runnable? = null) {
+        duration = DEFAULT_DURATION
         isStarted = true
         super.startRipple(onAnimationEnd)
     }
@@ -50,6 +50,7 @@
         if (!isStarted) {
             return // Ignore if ripple is not started yet.
         }
+        duration = DEFAULT_DURATION
         // Reset all listeners to animator.
         animator.removeAllListeners()
         animator.addListener(object : AnimatorListenerAdapter() {
@@ -71,15 +72,16 @@
         animator.removeAllUpdateListeners()
 
         // Only show the outline as ripple expands and disappears when animation ends.
-        setRippleFill(false)
+        removeRippleFill()
 
         val startingPercentage = calculateStartingPercentage(newHeight)
+        animator.duration = EXPAND_TO_FULL_DURATION
         animator.addUpdateListener { updateListener ->
             val now = updateListener.currentPlayTime
             val progress = updateListener.animatedValue as Float
-            rippleShader.progress = startingPercentage + (progress * (1 - startingPercentage))
-            rippleShader.distortionStrength = 1 - rippleShader.progress
-            rippleShader.pixelDensity = 1 - rippleShader.progress
+            rippleShader.rawProgress = startingPercentage + (progress * (1 - startingPercentage))
+            rippleShader.distortionStrength = 1 - rippleShader.rawProgress
+            rippleShader.pixelDensity = 1 - rippleShader.rawProgress
             rippleShader.time = now.toFloat()
             invalidate()
         }
@@ -100,4 +102,35 @@
         val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat()
         return 1 - remainingPercentage
     }
+
+    private fun setupRippleFadeParams() {
+        with(rippleShader) {
+            // No fade out for the base ring.
+            baseRingFadeParams.fadeOutStart = 1f
+            baseRingFadeParams.fadeOutEnd = 1f
+
+            // No fade in and outs for the center fill, as we always draw it.
+            centerFillFadeParams.fadeInStart = 0f
+            centerFillFadeParams.fadeInEnd = 0f
+            centerFillFadeParams.fadeOutStart = 1f
+            centerFillFadeParams.fadeOutEnd = 1f
+        }
+    }
+
+    private fun removeRippleFill() {
+        with(rippleShader) {
+            baseRingFadeParams.fadeOutStart = RippleShader.DEFAULT_BASE_RING_FADE_OUT_START
+            baseRingFadeParams.fadeOutEnd = RippleShader.DEFAULT_FADE_OUT_END
+
+            centerFillFadeParams.fadeInStart = RippleShader.DEFAULT_FADE_IN_START
+            centerFillFadeParams.fadeInEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_IN_END
+            centerFillFadeParams.fadeOutStart = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_START
+            centerFillFadeParams.fadeOutEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_END
+        }
+    }
+
+    companion object {
+        const val DEFAULT_DURATION = 333L
+        const val EXPAND_TO_FULL_DURATION = 1000L
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 1f27582..537dbb9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -136,9 +136,9 @@
         ),
     ) {
         override fun isValidNextState(nextState: ChipStateSender): Boolean {
-            return nextState == FAR_FROM_RECEIVER ||
-                    nextState == ALMOST_CLOSE_TO_START_CAST ||
-                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+            // Since _SUCCEEDED is the end of a transfer sequence, we should be able to move to any
+            // state that represents the beginning of a new sequence.
+            return stateIsStartOfSequence(nextState)
         }
     },
 
@@ -158,9 +158,9 @@
         ),
     ) {
         override fun isValidNextState(nextState: ChipStateSender): Boolean {
-            return nextState == FAR_FROM_RECEIVER ||
-                    nextState == ALMOST_CLOSE_TO_END_CAST ||
-                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+            // Since _SUCCEEDED is the end of a transfer sequence, we should be able to move to any
+            // state that represents the beginning of a new sequence.
+            return stateIsStartOfSequence(nextState)
         }
     },
 
@@ -173,9 +173,9 @@
         endItem = SenderEndItem.Error,
     ) {
         override fun isValidNextState(nextState: ChipStateSender): Boolean {
-            return nextState == FAR_FROM_RECEIVER ||
-                    nextState == ALMOST_CLOSE_TO_START_CAST ||
-                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+            // Since _FAILED is the end of a transfer sequence, we should be able to move to any
+            // state that represents the beginning of a new sequence.
+            return stateIsStartOfSequence(nextState)
         }
     },
 
@@ -188,9 +188,9 @@
         endItem = SenderEndItem.Error,
     ) {
         override fun isValidNextState(nextState: ChipStateSender): Boolean {
-            return nextState == FAR_FROM_RECEIVER ||
-                    nextState == ALMOST_CLOSE_TO_END_CAST ||
-                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+            // Since _FAILED is the end of a transfer sequence, we should be able to move to any
+            // state that represents the beginning of a new sequence.
+            return stateIsStartOfSequence(nextState)
         }
     },
 
@@ -210,9 +210,9 @@
         }
 
         override fun isValidNextState(nextState: ChipStateSender): Boolean {
-            return nextState == FAR_FROM_RECEIVER ||
-                    nextState.transferStatus == TransferStatus.NOT_STARTED ||
-                    nextState.transferStatus == TransferStatus.IN_PROGRESS
+            // When far away, we can go to any state that represents the start of a transfer
+            // sequence.
+            return stateIsStartOfSequence(nextState)
         }
     };
 
@@ -227,6 +227,20 @@
         return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
     }
 
+    /**
+     * Returns true if moving from this state to [nextState] is a valid transition.
+     *
+     * In general, we expect a media transfer go to through a sequence of states:
+     * For push-to-receiver:
+     *   - ALMOST_CLOSE_TO_START_CAST => TRANSFER_TO_RECEIVER_TRIGGERED =>
+     *     TRANSFER_TO_RECEIVER_(SUCCEEDED|FAILED)
+     *   - ALMOST_CLOSE_TO_END_CAST => TRANSFER_TO_THIS_DEVICE_TRIGGERED =>
+     *     TRANSFER_TO_THIS_DEVICE_(SUCCEEDED|FAILED)
+     *
+     * This method should validate that the states go through approximately that sequence.
+     *
+     * See b/221265848 for more details.
+     */
     abstract fun isValidNextState(nextState: ChipStateSender): Boolean
 
     companion object {
@@ -276,6 +290,18 @@
 
             return currentState.isValidNextState(desiredState)
         }
+
+        /**
+         * Returns true if [state] represents a state at the beginning of a sequence and false
+         * otherwise.
+         */
+        private fun stateIsStartOfSequence(state: ChipStateSender): Boolean {
+            return state == FAR_FROM_RECEIVER ||
+                state.transferStatus == TransferStatus.NOT_STARTED ||
+                // It's possible to skip the NOT_STARTED phase and go immediately into the
+                // IN_PROGRESS phase.
+                state.transferStatus == TransferStatus.IN_PROGRESS
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 935f38d..89ca5d3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -23,17 +23,20 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.statusbar.IUndoMediaTransferCallback
 import com.android.systemui.CoreStartable
+import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
 import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import java.io.PrintWriter
 import javax.inject.Inject
 
 /**
@@ -47,12 +50,12 @@
     private val chipbarCoordinator: ChipbarCoordinator,
     private val commandQueue: CommandQueue,
     private val context: Context,
-    @MediaTttSenderLogger private val logger: MediaTttLogger<ChipbarInfo>,
+    private val dumpManager: DumpManager,
+    private val logger: MediaTttSenderLogger,
     private val mediaTttFlags: MediaTttFlags,
     private val uiEventLogger: MediaTttSenderUiEventLogger,
-) : CoreStartable {
+) : CoreStartable, Dumpable {
 
-    private var displayedState: ChipStateSender? = null
     // A map to store current chip state per id.
     private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
 
@@ -74,6 +77,7 @@
     override fun start() {
         if (mediaTttFlags.isMediaTttEnabled()) {
             commandQueue.addCallback(commandQueueCallbacks)
+            dumpManager.registerNormalDumpable(this)
         }
     }
 
@@ -91,42 +95,42 @@
             return
         }
 
-        val currentState = stateMap[routeInfo.id]
-        if (!ChipStateSender.isValidStateTransition(currentState, chipState)) {
+        val currentStateForId: ChipStateSender? = stateMap[routeInfo.id]
+        if (!ChipStateSender.isValidStateTransition(currentStateForId, chipState)) {
             // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
             logger.logInvalidStateTransitionError(
-                currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
+                currentState = currentStateForId?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
                 chipState.name
             )
             return
         }
         uiEventLogger.logSenderStateChange(chipState)
 
-        stateMap.put(routeInfo.id, chipState)
         if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
-            // No need to store the state since it is the default state
-            stateMap.remove(routeInfo.id)
-            // Return early if we're not displaying a chip anyway
-            val currentDisplayedState = displayedState ?: return
+            // Return early if we're not displaying a chip for this ID anyway
+            if (currentStateForId == null) return
 
             val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name
             if (
-                currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS ||
-                    currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED
+                currentStateForId.transferStatus == TransferStatus.IN_PROGRESS ||
+                    currentStateForId.transferStatus == TransferStatus.SUCCEEDED
             ) {
                 // Don't remove the chip if we're in progress or succeeded, since the user should
                 // still be able to see the status of the transfer.
                 logger.logRemovalBypass(
                     removalReason,
-                    bypassReason = "transferStatus=${currentDisplayedState.transferStatus.name}"
+                    bypassReason = "transferStatus=${currentStateForId.transferStatus.name}"
                 )
                 return
             }
 
-            displayedState = null
+            // No need to store the state since it is the default state
+            removeIdFromStore(routeInfo.id, reason = removalReason)
             chipbarCoordinator.removeView(routeInfo.id, removalReason)
         } else {
-            displayedState = chipState
+            stateMap[routeInfo.id] = chipState
+            logger.logStateMap(stateMap)
+            chipbarCoordinator.registerListener(displayListener)
             chipbarCoordinator.displayView(
                 createChipbarInfo(
                     chipState,
@@ -135,7 +139,7 @@
                     context,
                     logger,
                 )
-            ) { stateMap.remove(routeInfo.id) }
+            )
         }
     }
 
@@ -147,7 +151,7 @@
         routeInfo: MediaRoute2Info,
         undoCallback: IUndoMediaTransferCallback?,
         context: Context,
-        logger: MediaTttLogger<ChipbarInfo>,
+        logger: MediaTttSenderLogger,
     ): ChipbarInfo {
         val packageName = routeInfo.clientPackageName
         val otherDeviceName =
@@ -156,12 +160,14 @@
             } else {
                 routeInfo.name.toString()
             }
+        val icon =
+            MediaTttUtils.getIconInfoFromPackageName(context, packageName) {
+                logger.logPackageNotFound(packageName)
+            }
 
         return ChipbarInfo(
             // Display the app's icon as the start icon
-            startIcon =
-                MediaTttUtils.getIconInfoFromPackageName(context, packageName, logger)
-                    .toTintedIcon(),
+            startIcon = icon.toTintedIcon(),
             text = chipStateSender.getChipTextString(context, otherDeviceName),
             endItem =
                 when (chipStateSender.endItem) {
@@ -182,6 +188,7 @@
                     }
                 },
             vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
+            allowSwipeToDismiss = true,
             windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
             wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
             timeoutMs = chipStateSender.timeout,
@@ -225,4 +232,21 @@
             onClickListener,
         )
     }
+
+    private val displayListener =
+        TemporaryViewDisplayController.Listener { id, reason -> removeIdFromStore(id, reason) }
+
+    private fun removeIdFromStore(id: String, reason: String) {
+        logger.logStateMapRemoval(id, reason)
+        stateMap.remove(id)
+        logger.logStateMap(stateMap)
+        if (stateMap.isEmpty()) {
+            chipbarCoordinator.unregisterListener(displayListener)
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("Current sender states:")
+        pw.println(stateMap.toString())
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
rename to packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
index bf216c6..a262e97 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTttSenderLogBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogBuffer.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.log.dagger;
+package com.android.systemui.media.taptotransfer.sender;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@@ -26,8 +26,7 @@
 import javax.inject.Qualifier;
 
 /**
- * A {@link LogBuffer} for
- * {@link com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger}.
+ * A {@link LogBuffer} for sender logs.
  */
 @Qualifier
 @Documented
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
index 4393af9..964a95b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt
@@ -13,14 +13,102 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.systemui.media.taptotransfer.sender
 
-import java.lang.annotation.Documented
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
-import javax.inject.Qualifier
+import android.app.StatusBarManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
 
-@Qualifier
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-annotation class MediaTttSenderLogger
+/** A logger for all events related to the media tap-to-transfer sender experience. */
+@SysUISingleton
+class MediaTttSenderLogger
+@Inject
+constructor(
+    @MediaTttSenderLogBuffer private val buffer: LogBuffer,
+) {
+    /** Logs a change in the chip state for the given [mediaRouteId]. */
+    fun logStateChange(
+        stateName: String,
+        mediaRouteId: String,
+        packageName: String?,
+    ) {
+        MediaTttLoggerUtils.logStateChange(buffer, TAG, stateName, mediaRouteId, packageName)
+    }
+
+    /** Logs an error in trying to update to [displayState]. */
+    fun logStateChangeError(@StatusBarManager.MediaTransferSenderState displayState: Int) {
+        MediaTttLoggerUtils.logStateChangeError(buffer, TAG, displayState)
+    }
+
+    /** Logs that we couldn't find information for [packageName]. */
+    fun logPackageNotFound(packageName: String) {
+        MediaTttLoggerUtils.logPackageNotFound(buffer, TAG, packageName)
+    }
+
+    /**
+     * Logs an invalid sender state transition error in trying to update to [desiredState].
+     *
+     * @param currentState the previous state of the chip.
+     * @param desiredState the new state of the chip.
+     */
+    fun logInvalidStateTransitionError(currentState: String, desiredState: String) {
+        buffer.log(
+            TAG,
+            LogLevel.ERROR,
+            {
+                str1 = currentState
+                str2 = desiredState
+            },
+            { "Cannot display state=$str2 after state=$str1; invalid transition" }
+        )
+    }
+
+    /**
+     * Logs that a removal request has been bypassed (ignored).
+     *
+     * @param removalReason the reason that the chip removal was requested.
+     * @param bypassReason the reason that the request was bypassed.
+     */
+    fun logRemovalBypass(removalReason: String, bypassReason: String) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = removalReason
+                str2 = bypassReason
+            },
+            { "Chip removal requested due to $str1; however, removal was ignored because $str2" }
+        )
+    }
+
+    /** Logs the current contents of the state map. */
+    fun logStateMap(map: Map<String, ChipStateSender>) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = map.toString() },
+            { "Current sender states: $str1" }
+        )
+    }
+
+    /** Logs that [id] has been removed from the state map due to [reason]. */
+    fun logStateMapRemoval(id: String, reason: String) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = id
+                str2 = reason
+            },
+            { "State removal: id=$str1 reason=$str2" }
+        )
+    }
+
+    companion object {
+        private const val TAG = "MediaTttSender"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 1d86343..1678c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -24,16 +24,19 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.MediaProjectionAppSelectorActivity
 import com.android.systemui.media.MediaProjectionAppSelectorActivity.Companion.EXTRA_HOST_APP_USER_HANDLE
+import com.android.systemui.media.MediaProjectionPermissionActivity
+import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
 import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
 import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
 import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
 import com.android.systemui.mediaprojection.appselector.data.ShellRecentTaskListProvider
 import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule
+import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.Binds
@@ -43,23 +46,21 @@
 import dagger.Subcomponent
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
-import java.lang.IllegalArgumentException
-import javax.inject.Qualifier
-import javax.inject.Scope
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.SupervisorJob
+import javax.inject.Qualifier
+import javax.inject.Scope
 
 @Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
 
 @Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
 
-@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
-
-@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
-
 @Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
 
-@Module(subcomponents = [MediaProjectionAppSelectorComponent::class])
+@Module(
+    subcomponents = [MediaProjectionAppSelectorComponent::class],
+    includes = [MediaProjectionDevicePolicyModule::class]
+)
 interface MediaProjectionModule {
     @Binds
     @IntoMap
@@ -67,11 +68,17 @@
     fun provideMediaProjectionAppSelectorActivity(
         activity: MediaProjectionAppSelectorActivity
     ): Activity
+
+    @Binds
+    @IntoMap
+    @ClassKey(MediaProjectionPermissionActivity::class)
+    fun bindsMediaProjectionPermissionActivity(impl: MediaProjectionPermissionActivity): Activity
 }
 
-/** Scoped values for [MediaProjectionAppSelectorComponent].
- *  We create a scope for the activity so certain dependencies like [TaskPreviewSizeProvider]
- *  could be reused. */
+/**
+ * Scoped values for [MediaProjectionAppSelectorComponent]. We create a scope for the activity so
+ * certain dependencies like [TaskPreviewSizeProvider] could be reused.
+ */
 @Module
 interface MediaProjectionAppSelectorModule {
 
@@ -83,6 +90,10 @@
 
     @Binds
     @MediaProjectionAppSelectorScope
+    fun bindRecentTaskLabelLoader(impl: ActivityTaskManagerLabelLoader): RecentTaskLabelLoader
+
+    @Binds
+    @MediaProjectionAppSelectorScope
     fun bindRecentTaskListProvider(impl: ShellRecentTaskListProvider): RecentTaskListProvider
 
     @Binds
@@ -104,20 +115,6 @@
         ): ConfigurationController = ConfigurationControllerImpl(activity)
 
         @Provides
-        @PersonalProfile
-        @MediaProjectionAppSelectorScope
-        fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
-            // Current foreground user is the 'personal' profile
-            return UserHandle.of(activityManagerWrapper.currentUserId)
-        }
-
-        @Provides
-        @WorkProfile
-        @MediaProjectionAppSelectorScope
-        fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
-            userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
-
-        @Provides
         @HostUserHandle
         @MediaProjectionAppSelectorScope
         fun hostUserHandle(activity: MediaProjectionAppSelectorActivity): UserHandle {
@@ -125,8 +122,10 @@
                 activity.intent.extras
                     ?: error("MediaProjectionAppSelectorActivity should be launched with extras")
             return extras.getParcelable(EXTRA_HOST_APP_USER_HANDLE)
-                ?: error("MediaProjectionAppSelectorActivity should be provided with " +
-                        "$EXTRA_HOST_APP_USER_HANDLE extra")
+                ?: error(
+                    "MediaProjectionAppSelectorActivity should be provided with " +
+                        "$EXTRA_HOST_APP_USER_HANDLE extra"
+                )
         }
 
         @Provides fun bindIconFactory(context: Context): IconFactory = IconFactory.obtain(context)
@@ -146,9 +145,7 @@
     /** Generates [MediaProjectionAppSelectorComponent]. */
     @Subcomponent.Factory
     interface Factory {
-        /**
-         * Create a factory to inject the activity into the graph
-         */
+        /** Create a factory to inject the activity into the graph */
         fun create(
             @BindsInstance activity: MediaProjectionAppSelectorActivity,
             @BindsInstance view: MediaProjectionAppSelectorView,
@@ -158,6 +155,7 @@
 
     val controller: MediaProjectionAppSelectorController
     val recentsViewController: MediaProjectionRecentsViewController
+    val emptyStateProvider: MediaProjectionBlockerEmptyStateProvider
     @get:HostUserHandle val hostUserHandle: UserHandle
     @get:PersonalProfile val personalProfileUserHandle: UserHandle
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
new file mode 100644
index 0000000..829b0dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.mediaprojection.appselector
+
+import android.content.Context
+import android.os.UserHandle
+import com.android.internal.R as AndroidR
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
+import com.android.internal.app.ResolverListAdapter
+import com.android.systemui.R
+import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import javax.inject.Inject
+
+@MediaProjectionAppSelectorScope
+class MediaProjectionBlockerEmptyStateProvider
+@Inject
+constructor(
+    @HostUserHandle private val hostAppHandle: UserHandle,
+    @PersonalProfile private val personalProfileHandle: UserHandle,
+    private val policyResolver: ScreenCaptureDevicePolicyResolver,
+    private val context: Context
+) : EmptyStateProvider {
+
+    override fun getEmptyState(resolverListAdapter: ResolverListAdapter): EmptyState? {
+        val screenCaptureAllowed =
+            policyResolver.isScreenCaptureAllowed(
+                targetAppUserHandle = resolverListAdapter.userHandle,
+                hostAppUserHandle = hostAppHandle
+            )
+
+        val isHostAppInPersonalProfile = hostAppHandle == personalProfileHandle
+
+        val subtitle =
+            if (isHostAppInPersonalProfile) {
+                AndroidR.string.resolver_cant_share_with_personal_apps_explanation
+            } else {
+                AndroidR.string.resolver_cant_share_with_work_apps_explanation
+            }
+
+        if (!screenCaptureAllowed) {
+            return object : EmptyState {
+                override fun getSubtitle(): String = context.resources.getString(subtitle)
+                override fun getTitle(): String =
+                    context.resources.getString(
+                        R.string.screen_capturing_disabled_by_policy_dialog_title
+                    )
+                override fun onEmptyStateShown() {
+                    // TODO(b/237397740) report analytics
+                }
+                override fun shouldSkipDataRebuild(): Boolean = true
+            }
+        }
+        return null
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt
new file mode 100644
index 0000000..eadcb93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskLabelLoader.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.systemui.mediaprojection.appselector.data
+
+import android.annotation.UserIdInt
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+interface RecentTaskLabelLoader {
+    suspend fun loadLabel(userId: Int, componentName: ComponentName): CharSequence?
+}
+
+class ActivityTaskManagerLabelLoader
+@Inject
+constructor(
+    @Background private val coroutineDispatcher: CoroutineDispatcher,
+    private val packageManager: PackageManager
+) : RecentTaskLabelLoader {
+
+    override suspend fun loadLabel(
+        @UserIdInt userId: Int,
+        componentName: ComponentName
+    ): CharSequence? =
+        withContext(coroutineDispatcher) {
+            val userHandle = UserHandle(userId)
+            val appInfo =
+                packageManager.getApplicationInfo(
+                    componentName.packageName,
+                    PackageManager.ApplicationInfoFlags.of(0 /* no flags */)
+                )
+            val label = packageManager.getApplicationLabel(appInfo)
+            return@withContext packageManager.getUserBadgedLabel(label, userHandle)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index 15cfeee..64f97f2 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -20,11 +20,12 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
-import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
 import com.android.systemui.R
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
 import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
+import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import dagger.assisted.Assisted
@@ -40,9 +41,10 @@
     @Assisted private val root: ViewGroup,
     private val iconLoader: AppIconLoader,
     private val thumbnailLoader: RecentTaskThumbnailLoader,
+    private val labelLoader: RecentTaskLabelLoader,
     private val taskViewSizeProvider: TaskPreviewSizeProvider,
     @MediaProjectionAppSelector private val scope: CoroutineScope
-) : RecyclerView.ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener {
+) : ViewHolder(root), ConfigurationListener, TaskPreviewSizeProvider.TaskPreviewSizeListener {
 
     val thumbnailView: MediaProjectionTaskView = root.requireViewById(R.id.task_thumbnail)
     private val iconView: ImageView = root.requireViewById(R.id.task_icon)
@@ -64,6 +66,10 @@
                         val icon = iconLoader.loadIcon(task.userId, component)
                         iconView.setImageDrawable(icon)
                     }
+                    launch {
+                        val label = labelLoader.loadLabel(task.userId, component)
+                        root.contentDescription = label
+                    }
                 }
                 launch {
                     val thumbnail = thumbnailLoader.loadThumbnail(task.taskId)
@@ -88,10 +94,10 @@
 
     private fun updateThumbnailSize() {
         thumbnailView.layoutParams =
-                thumbnailView.layoutParams.apply {
-                    width = taskViewSizeProvider.size.width()
-                    height = taskViewSizeProvider.size.height()
-                }
+            thumbnailView.layoutParams.apply {
+                width = taskViewSizeProvider.size.width()
+                height = taskViewSizeProvider.size.height()
+            }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt
new file mode 100644
index 0000000..13b71a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/MediaProjectionDevicePolicyModule.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.systemui.mediaprojection.devicepolicy
+
+import android.os.UserHandle
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import dagger.Module
+import dagger.Provides
+import javax.inject.Qualifier
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class WorkProfile
+
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class PersonalProfile
+
+/** Module for media projection device policy related dependencies */
+@Module
+class MediaProjectionDevicePolicyModule {
+    @Provides
+    @PersonalProfile
+    fun personalUserHandle(activityManagerWrapper: ActivityManagerWrapper): UserHandle {
+        // Current foreground user is the 'personal' profile
+        return UserHandle.of(activityManagerWrapper.currentUserId)
+    }
+
+    @Provides
+    @WorkProfile
+    fun workProfileUserHandle(userTracker: UserTracker): UserHandle? =
+        userTracker.userProfiles.find { it.isManagedProfile }?.userHandle
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt
new file mode 100644
index 0000000..6bd33e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.systemui.mediaprojection.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.os.UserHandle
+import android.os.UserManager
+import javax.inject.Inject
+
+/**
+ * Utility class to resolve if screen capture allowed for a particular target app/host app pair. It
+ * caches the state of the policies, so you need to create a new instance of this class if you want
+ * to react to updated policies state.
+ */
+class ScreenCaptureDevicePolicyResolver
+@Inject
+constructor(
+    private val devicePolicyManager: DevicePolicyManager,
+    private val userManager: UserManager,
+    @PersonalProfile private val personalProfileUserHandle: UserHandle,
+    @WorkProfile private val workProfileUserHandle: UserHandle?
+) {
+
+    /**
+     * Returns true if [hostAppUserHandle] is allowed to perform screen capture of
+     * [targetAppUserHandle]
+     */
+    fun isScreenCaptureAllowed(
+        targetAppUserHandle: UserHandle,
+        hostAppUserHandle: UserHandle,
+    ): Boolean {
+        if (hostAppUserHandle.isWorkProfile() && workProfileScreenCaptureDisabled) {
+            // Disable screen capturing as host apps should not capture the screen
+            return false
+        }
+
+        if (!hostAppUserHandle.isWorkProfile() && personalProfileScreenCaptureDisabled) {
+            // Disable screen capturing as personal apps should not capture the screen
+            return false
+        }
+
+        if (targetAppUserHandle.isWorkProfile()) {
+            // Work profile target
+            if (workProfileScreenCaptureDisabled) {
+                // Do not allow sharing work profile apps as work profile capturing is disabled
+                return false
+            }
+        } else {
+            // Personal profile target
+            if (hostAppUserHandle.isWorkProfile() && disallowSharingIntoManagedProfile) {
+                // Do not allow sharing of personal apps into work profile apps
+                return false
+            }
+
+            if (personalProfileScreenCaptureDisabled) {
+                // Disable screen capturing as personal apps should not be captured
+                return false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Returns true if [hostAppUserHandle] is NOT allowed to capture an app from any profile,
+     * could be useful to finish the screen capture flow as soon as possible when the screen
+     * could not be captured at all.
+     */
+    fun isScreenCaptureCompletelyDisabled(hostAppUserHandle: UserHandle): Boolean {
+        val isWorkAppsCaptureDisabled =
+                if (workProfileUserHandle != null) {
+                    !isScreenCaptureAllowed(
+                            targetAppUserHandle = workProfileUserHandle,
+                            hostAppUserHandle = hostAppUserHandle
+                    )
+                } else true
+
+        val isPersonalAppsCaptureDisabled =
+                !isScreenCaptureAllowed(
+                        targetAppUserHandle = personalProfileUserHandle,
+                        hostAppUserHandle = hostAppUserHandle
+                )
+
+        return isWorkAppsCaptureDisabled && isPersonalAppsCaptureDisabled
+    }
+
+    private val personalProfileScreenCaptureDisabled: Boolean by lazy {
+        devicePolicyManager.getScreenCaptureDisabled(
+            /* admin */ null,
+            personalProfileUserHandle.identifier
+        )
+    }
+
+    private val workProfileScreenCaptureDisabled: Boolean by lazy {
+        workProfileUserHandle?.let {
+            devicePolicyManager.getScreenCaptureDisabled(/* admin */ null, it.identifier)
+        }
+            ?: false
+    }
+
+    private val disallowSharingIntoManagedProfile: Boolean by lazy {
+        workProfileUserHandle?.let {
+            userManager.hasUserRestrictionForUser(
+                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+                it
+            )
+        }
+            ?: false
+    }
+
+    private fun UserHandle?.isWorkProfile(): Boolean = this == workProfileUserHandle
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
new file mode 100644
index 0000000..a6b3da0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.systemui.mediaprojection.devicepolicy
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Dialog that shows that screen capture is disabled on this device. */
+class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) {
+
+    init {
+        setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
+        setMessage(
+            context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
+        )
+        setIcon(R.drawable.ic_cast)
+        setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index 3ecf154..8d80990 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -16,13 +16,12 @@
 
 package com.android.systemui.model;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import android.annotation.NonNull;
 import android.util.Log;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.PrintWriter;
@@ -39,11 +38,16 @@
     private static final String TAG = SysUiState.class.getSimpleName();
     public static final boolean DEBUG = false;
 
+    private final DisplayTracker mDisplayTracker;
     private @QuickStepContract.SystemUiStateFlags int mFlags;
     private final List<SysUiStateCallback> mCallbacks = new ArrayList<>();
     private int mFlagsToSet = 0;
     private int mFlagsToClear = 0;
 
+    public SysUiState(DisplayTracker displayTracker) {
+        mDisplayTracker = displayTracker;
+    }
+
     /**
      * Add listener to be notified of changes made to SysUI state.
      * The callback will also be called as part of this function.
@@ -81,7 +85,7 @@
     }
 
     private void updateFlags(int displayId) {
-        if (displayId != DEFAULT_DISPLAY) {
+        if (displayId != mDisplayTracker.getDefaultDisplayId()) {
             // Ignore non-default displays for now
             Log.w(TAG, "Ignoring flag update for display: " + displayId, new Throwable());
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
index 1324d2c..c4a1ed4 100644
--- a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
@@ -19,7 +19,6 @@
 import android.view.WindowManagerGlobal
 import com.android.app.motiontool.DdmHandleMotionTool
 import com.android.app.motiontool.MotionToolManager
-import com.android.app.viewcapture.ViewCapture
 import com.android.systemui.CoreStartable
 import dagger.Binds
 import dagger.Module
@@ -38,17 +37,12 @@
         }
 
         @Provides
-        fun provideMotionToolManager(
-            viewCapture: ViewCapture,
-            windowManagerGlobal: WindowManagerGlobal
-        ): MotionToolManager {
-            return MotionToolManager.getInstance(viewCapture, windowManagerGlobal)
+        fun provideMotionToolManager(windowManagerGlobal: WindowManagerGlobal): MotionToolManager {
+            return MotionToolManager.getInstance(windowManagerGlobal)
         }
 
         @Provides
         fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance()
-
-        @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance()
     }
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index a92203c..1121e160 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -297,7 +297,7 @@
 
     private void updateAssistantAvailability() {
         boolean assistantAvailableForUser = mAssistManagerLazy.get()
-                .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
+                .getAssistInfoForUser(mUserTracker.getUserId()) != null;
         boolean longPressDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault);
         mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 61bb858..97c290d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -25,7 +25,6 @@
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -72,7 +71,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
@@ -131,6 +129,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
@@ -219,6 +218,7 @@
     private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
     private final UserContextProvider mUserContextProvider;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final DisplayTracker mDisplayTracker;
     private final RegionSamplingHelper mRegionSamplingHelper;
     private final int mNavColorSampleMargin;
     private NavigationBarFrame mFrame;
@@ -462,7 +462,8 @@
                 @Override
                 public void onStartedWakingUp() {
                     notifyScreenStateChanged(true);
-                    if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) {
+                    if (isGesturalModeOnDefaultDisplay(getContext(), mDisplayTracker,
+                            mNavBarMode)) {
                         mRegionSamplingHelper.start(mSamplingBounds);
                     }
                 }
@@ -546,7 +547,8 @@
             Optional<BackAnimation> backAnimation,
             UserContextProvider userContextProvider,
             WakefulnessLifecycle wakefulnessLifecycle,
-            TaskStackChangeListeners taskStackChangeListeners) {
+            TaskStackChangeListeners taskStackChangeListeners,
+            DisplayTracker displayTracker) {
         super(navigationBarView);
         mFrame = navigationBarFrame;
         mContext = context;
@@ -586,6 +588,7 @@
         mUserContextProvider = userContextProvider;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mTaskStackChangeListeners = taskStackChangeListeners;
+        mDisplayTracker = displayTracker;
 
         mNavColorSampleMargin = getResources()
                 .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -633,12 +636,14 @@
 
                     @Override
                     public boolean isSamplingEnabled() {
-                        return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);
+                        return isGesturalModeOnDefaultDisplay(getContext(), mDisplayTracker,
+                                mNavBarMode);
                     }
                 }, mainExecutor, bgExecutor);
 
         mView.setBackgroundExecutor(bgExecutor);
         mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler);
+        mView.setDisplayTracker(mDisplayTracker);
         mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);
     }
 
@@ -666,7 +671,7 @@
                 getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
                         .getRotation()));
         mDisplayId = mContext.getDisplayId();
-        mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
+        mIsOnDefaultDisplay = mDisplayId == mDisplayTracker.getDefaultDisplayId();
 
         // Ensure we try to get currentSysuiState from navBarHelper before command queue callbacks
         // start firing, since the latter is source of truth
@@ -1469,7 +1474,7 @@
     private void onAccessibilityClick(View v) {
         final Display display = v.getDisplay();
         mAccessibilityManager.notifyAccessibilityButtonClicked(
-                display != null ? display.getDisplayId() : DEFAULT_DISPLAY);
+                display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId());
     }
 
     private boolean onAccessibilityLongClick(View v) {
@@ -1477,7 +1482,7 @@
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
-        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+        mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
         return true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 5d43c5d..8c19111 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -19,12 +19,10 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
 import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -56,6 +54,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
@@ -63,8 +62,8 @@
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.settings.SecureSettings;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
 
@@ -87,6 +86,8 @@
     private final Handler mHandler;
     private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
     private FeatureFlags mFeatureFlags;
+    private final SecureSettings mSecureSettings;
+    private final DisplayTracker mDisplayTracker;
     private final DisplayManager mDisplayManager;
     private final TaskbarDelegate mTaskbarDelegate;
     private int mNavMode;
@@ -118,11 +119,15 @@
             TaskStackChangeListeners taskStackChangeListeners,
             Optional<Pip> pipOptional,
             Optional<BackAnimation> backAnimation,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            SecureSettings secureSettings,
+            DisplayTracker displayTracker) {
         mContext = context;
         mHandler = mainHandler;
         mNavigationBarComponentFactory = navigationBarComponentFactory;
         mFeatureFlags = featureFlags;
+        mSecureSettings = secureSettings;
+        mDisplayTracker = displayTracker;
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         commandQueue.addCallback(this);
         configurationController.addCallback(this);
@@ -192,8 +197,7 @@
     }
 
     private void updateAccessibilityButtonModeIfNeeded() {
-        ContentResolver contentResolver = mContext.getContentResolver();
-        final int mode = Settings.Secure.getIntForUser(contentResolver,
+        final int mode = mSecureSettings.getIntForUser(
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
                 ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
 
@@ -207,14 +211,14 @@
         // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE.
         if (QuickStepContract.isGesturalMode(mNavMode)
                 && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) {
-            Settings.Secure.putIntForUser(contentResolver,
+            mSecureSettings.putIntForUser(
                     Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE,
                     UserHandle.USER_CURRENT);
             // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to
             // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR.
         } else if (!QuickStepContract.isGesturalMode(mNavMode)
                 && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) {
-            Settings.Secure.putIntForUser(contentResolver,
+            mSecureSettings.putIntForUser(
                     Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
                     ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
         }
@@ -295,9 +299,10 @@
         // Don't need to create nav bar on the default display if we initialize TaskBar.
         final boolean shouldCreateDefaultNavbar = includeDefaultDisplay
                 && !initializeTaskbarIfNecessary();
-        Display[] displays = mDisplayManager.getDisplays();
+        Display[] displays = mDisplayTracker.getAllDisplays();
         for (Display display : displays) {
-            if (shouldCreateDefaultNavbar || display.getDisplayId() != DEFAULT_DISPLAY) {
+            if (shouldCreateDefaultNavbar
+                    || display.getDisplayId() != mDisplayTracker.getDefaultDisplayId()) {
                 createNavigationBar(display, null /* savedState */, result);
             }
         }
@@ -316,7 +321,7 @@
         }
 
         final int displayId = display.getDisplayId();
-        final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
+        final boolean isOnDefaultDisplay = displayId == mDisplayTracker.getDefaultDisplayId();
 
         // We may show TaskBar on the default display for large screen device. Don't need to create
         // navigation bar for this case.
@@ -411,7 +416,7 @@
 
     /** @return {@link NavigationBarView} on the default display. */
     public @Nullable NavigationBarView getDefaultNavigationBarView() {
-        return getNavigationBarView(DEFAULT_DISPLAY);
+        return getNavigationBarView(mDisplayTracker.getDefaultDisplayId());
     }
 
     /**
@@ -432,7 +437,8 @@
         final NavigationBarView navBarView = getNavigationBarView(displayId);
         if (navBarView != null) {
             navBarView.showPinningEnterExitToast(entering);
-        } else if (displayId == DEFAULT_DISPLAY && mTaskbarDelegate.isInitialized()) {
+        } else if (displayId == mDisplayTracker.getDefaultDisplayId()
+                && mTaskbarDelegate.isInitialized()) {
             mTaskbarDelegate.showPinningEnterExitToast(entering);
         }
     }
@@ -441,7 +447,8 @@
         final NavigationBarView navBarView = getNavigationBarView(displayId);
         if (navBarView != null) {
             navBarView.showPinningEscapeToast();
-        } else if (displayId == DEFAULT_DISPLAY && mTaskbarDelegate.isInitialized()) {
+        } else if (displayId == mDisplayTracker.getDefaultDisplayId()
+                && mTaskbarDelegate.isInitialized()) {
             mTaskbarDelegate.showPinningEscapeToast();
         }
     }
@@ -458,7 +465,7 @@
     /** @return {@link NavigationBar} on the default display. */
     @Nullable
     public NavigationBar getDefaultNavigationBar() {
-        return mNavigationBars.get(DEFAULT_DISPLAY);
+        return mNavigationBars.get(mDisplayTracker.getDefaultDisplayId());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
index 6793f01..a4de9ff 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
@@ -24,7 +24,6 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.SparseArray;
-import android.view.Display;
 import android.view.IWallpaperVisibilityListener;
 import android.view.IWindowManager;
 import android.view.View;
@@ -32,6 +31,7 @@
 import com.android.systemui.R;
 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 
@@ -65,6 +65,7 @@
     @org.jetbrains.annotations.NotNull
     private final IWindowManager mWindowManagerService;
     private final LightBarTransitionsController mLightTransitionsController;
+    private final DisplayTracker mDisplayTracker;
     private final boolean mAllowAutoDimWallpaperNotVisible;
     private boolean mWallpaperVisible;
 
@@ -89,18 +90,20 @@
     public NavigationBarTransitions(
             NavigationBarView view,
             IWindowManager windowManagerService,
-            LightBarTransitionsController.Factory lightBarTransitionsControllerFactory) {
+            LightBarTransitionsController.Factory lightBarTransitionsControllerFactory,
+            DisplayTracker displayTracker) {
         super(view, R.drawable.nav_background);
         mView = view;
         mWindowManagerService = windowManagerService;
         mLightTransitionsController = lightBarTransitionsControllerFactory.create(this);
+        mDisplayTracker = displayTracker;
         mAllowAutoDimWallpaperNotVisible = view.getContext().getResources()
                 .getBoolean(R.bool.config_navigation_bar_enable_auto_dim_no_visible_wallpaper);
         mDarkIntensityListeners = new ArrayList();
 
         try {
             mWallpaperVisible = mWindowManagerService.registerWallpaperVisibilityListener(
-                    mWallpaperVisibilityListener, Display.DEFAULT_DISPLAY);
+                    mWallpaperVisibilityListener, mDisplayTracker.getDefaultDisplayId());
         } catch (RemoteException e) {
         }
         mView.addOnLayoutChangeListener(
@@ -126,7 +129,7 @@
     public void destroy() {
         try {
             mWindowManagerService.unregisterWallpaperVisibilityListener(mWallpaperVisibilityListener,
-                    Display.DEFAULT_DISPLAY);
+                    mDisplayTracker.getDefaultDisplayId());
         } catch (RemoteException e) {
         }
         mLightTransitionsController.destroy();
@@ -135,7 +138,10 @@
     @Override
     public void setAutoDim(boolean autoDim) {
         // Ensure we aren't in gestural nav if we are triggering auto dim
-        if (autoDim && isGesturalModeOnDefaultDisplay(mView.getContext(), mNavBarMode)) return;
+        if (autoDim && isGesturalModeOnDefaultDisplay(mView.getContext(), mDisplayTracker,
+                mNavBarMode)) {
+            return;
+        }
         if (mAutoDim == autoDim) return;
         mAutoDim = autoDim;
         applyLightsOut(true, false);
@@ -219,7 +225,7 @@
 
     @Override
     public int getTintAnimationDuration() {
-        if (isGesturalModeOnDefaultDisplay(mView.getContext(), mNavBarMode)) {
+        if (isGesturalModeOnDefaultDisplay(mView.getContext(), mDisplayTracker, mNavBarMode)) {
             return Math.max(DEFAULT_COLOR_ADAPT_TRANSITION_TIME, MIN_COLOR_ADAPT_TRANSITION_TIME);
         }
         return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 88c4fd5..63fb499 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -16,13 +16,11 @@
 
 package com.android.systemui.navigationbar;
 
-import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
 import static android.inputmethodservice.InputMethodService.canImeRenderGesturalNavButtons;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
 
@@ -75,13 +73,12 @@
 import com.android.systemui.navigationbar.buttons.RotationContextButton;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shared.rotation.FloatingRotationButton;
 import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
 import com.android.systemui.shared.rotation.RotationButtonController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
@@ -127,6 +124,7 @@
     private int mDarkIconColor;
 
     private EdgeBackGestureHandler mEdgeBackGestureHandler;
+    private DisplayTracker mDisplayTracker;
     private final DeadZone mDeadZone;
     private NavigationBarTransitions mBarTransitions;
     @Nullable
@@ -303,7 +301,8 @@
                 R.dimen.floating_rotation_button_taskbar_left_margin,
                 R.dimen.floating_rotation_button_taskbar_bottom_margin,
                 R.dimen.floating_rotation_button_diameter,
-                R.dimen.key_button_ripple_max_width);
+                R.dimen.key_button_ripple_max_width,
+                R.bool.floating_rotation_button_position_left);
         mRotationButtonController = new RotationButtonController(mLightContext, mLightIconColor,
                 mDarkIconColor, R.drawable.ic_sysbar_rotate_button_ccw_start_0,
                 R.drawable.ic_sysbar_rotate_button_ccw_start_90,
@@ -361,6 +360,10 @@
         mBgExecutor = bgExecutor;
     }
 
+    public void setDisplayTracker(DisplayTracker displayTracker) {
+        mDisplayTracker = displayTracker;
+    }
+
     public void setTouchHandler(Gefingerpoken touchHandler) {
         mTouchHandler = touchHandler;
     }
@@ -558,7 +561,8 @@
     }
 
     public void setBehavior(@Behavior int behavior) {
-        mRotationButtonController.onBehaviorChanged(Display.DEFAULT_DISPLAY, behavior);
+        mRotationButtonController.onBehaviorChanged(mDisplayTracker.getDefaultDisplayId(),
+                behavior);
     }
 
     @Override
@@ -678,7 +682,7 @@
     @VisibleForTesting
     boolean isRecentsButtonDisabled() {
         return mUseCarModeUi || !isOverviewEnabled()
-                || getContext().getDisplayId() != Display.DEFAULT_DISPLAY;
+                || getContext().getDisplayId() != mDisplayTracker.getDefaultDisplayId();
     }
 
     private Display getContextDisplay() {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index 2822435..f335733 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -1,14 +1,20 @@
 package com.android.systemui.navigationbar.gestural
 
 import android.content.Context
+import android.content.res.Configuration
+import android.content.res.TypedArray
 import android.graphics.Canvas
 import android.graphics.Paint
 import android.graphics.Path
 import android.graphics.RectF
+import android.util.MathUtils.min
+import android.util.TypedValue
 import android.view.View
+import androidx.appcompat.view.ContextThemeWrapper
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import androidx.dynamicanimation.animation.SpringAnimation
 import androidx.dynamicanimation.animation.SpringForce
+import com.android.internal.R.style.Theme_DeviceDefault
 import com.android.internal.util.LatencyTracker
 import com.android.settingslib.Utils
 import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener
@@ -16,7 +22,10 @@
 private const val TAG = "BackPanel"
 private const val DEBUG = false
 
-class BackPanel(context: Context, private val latencyTracker: LatencyTracker) : View(context) {
+class BackPanel(
+        context: Context,
+        private val latencyTracker: LatencyTracker
+) : View(context) {
 
     var arrowsPointLeft = false
         set(value) {
@@ -45,52 +54,54 @@
     /**
      * The length of the arrow measured horizontally. Used for animating [arrowPath]
      */
-    private var arrowLength = AnimatedFloat("arrowLength", SpringForce())
+    private var arrowLength = AnimatedFloat(
+            name = "arrowLength",
+            minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS
+    )
 
     /**
      * The height of the arrow measured vertically from its center to its top (i.e. half the total
      * height). Used for animating [arrowPath]
      */
-    private var arrowHeight = AnimatedFloat("arrowHeight", SpringForce())
-
-    private val backgroundWidth = AnimatedFloat(
-        name = "backgroundWidth",
-        SpringForce().apply {
-            stiffness = 600f
-            dampingRatio = 0.65f
-        }
+    var arrowHeight = AnimatedFloat(
+            name = "arrowHeight",
+            minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ROTATION_DEGREES
     )
 
-    private val backgroundHeight = AnimatedFloat(
-        name = "backgroundHeight",
-        SpringForce().apply {
-            stiffness = 600f
-            dampingRatio = 0.65f
-        }
+    val backgroundWidth = AnimatedFloat(
+            name = "backgroundWidth",
+            minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
+            minimumValue = 0f,
+    )
+
+    val backgroundHeight = AnimatedFloat(
+            name = "backgroundHeight",
+            minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
+            minimumValue = 0f,
     )
 
     /**
      * Corners of the background closer to the edge of the screen (where the arrow appeared from).
      * Used for animating [arrowBackgroundRect]
      */
-    private val backgroundEdgeCornerRadius = AnimatedFloat(
-        name = "backgroundEdgeCornerRadius",
-        SpringForce().apply {
-            stiffness = 400f
-            dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
-        }
-    )
+    val backgroundEdgeCornerRadius = AnimatedFloat("backgroundEdgeCornerRadius")
 
     /**
      * Corners of the background further from the edge of the screens (toward the direction the
      * arrow is being dragged). Used for animating [arrowBackgroundRect]
      */
-    private val backgroundFarCornerRadius = AnimatedFloat(
-        name = "backgroundDragCornerRadius",
-        SpringForce().apply {
-            stiffness = 2200f
-            dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
-        }
+    val backgroundFarCornerRadius = AnimatedFloat("backgroundFarCornerRadius")
+
+    var scale = AnimatedFloat(
+            name = "scale",
+            minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_SCALE,
+            minimumValue = 0f
+    )
+
+    val scalePivotX = AnimatedFloat(
+            name = "scalePivotX",
+            minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
+            minimumValue = backgroundWidth.pos / 2,
     )
 
     /**
@@ -98,34 +109,40 @@
      * background's margin relative to the screen edge. The arrow will be centered within the
      * background.
      */
-    private var horizontalTranslation = AnimatedFloat("horizontalTranslation", SpringForce())
+    var horizontalTranslation = AnimatedFloat(name = "horizontalTranslation")
 
-    private val currentAlpha: FloatPropertyCompat<BackPanel> =
-        object : FloatPropertyCompat<BackPanel>("currentAlpha") {
-            override fun setValue(panel: BackPanel, value: Float) {
-                panel.alpha = value
-            }
+    var arrowAlpha = AnimatedFloat(
+            name = "arrowAlpha",
+            minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
+            minimumValue = 0f,
+            maximumValue = 1f
+    )
 
-            override fun getValue(panel: BackPanel): Float = panel.alpha
-        }
+    val backgroundAlpha = AnimatedFloat(
+            name = "backgroundAlpha",
+            minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
+            minimumValue = 0f,
+            maximumValue = 1f
+    )
 
-    private val alphaAnimation = SpringAnimation(this, currentAlpha)
-        .setSpring(
-            SpringForce()
-                .setStiffness(60f)
-                .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
-        )
+    private val allAnimatedFloat = setOf(
+            arrowLength,
+            arrowHeight,
+            backgroundWidth,
+            backgroundEdgeCornerRadius,
+            backgroundFarCornerRadius,
+            scalePivotX,
+            scale,
+            horizontalTranslation,
+            arrowAlpha,
+            backgroundAlpha
+    )
 
     /**
      * Canvas vertical translation. How far up/down the arrow and background appear relative to the
      * canvas.
      */
-    private var verticalTranslation: AnimatedFloat = AnimatedFloat(
-        name = "verticalTranslation",
-        SpringForce().apply {
-            stiffness = SpringForce.STIFFNESS_MEDIUM
-        }
-    )
+    var verticalTranslation = AnimatedFloat("verticalTranslation")
 
     /**
      * Use for drawing debug info. Can only be set if [DEBUG]=true
@@ -136,28 +153,67 @@
         }
 
     internal fun updateArrowPaint(arrowThickness: Float) {
-        // Arrow constants
+
         arrowPaint.strokeWidth = arrowThickness
 
-        arrowPaint.color =
-            Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
-        arrowBackgroundPaint.color = Utils.getColorAccentDefaultColor(context)
+        val isDeviceInNightTheme = resources.configuration.uiMode and
+                Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+
+        val colorControlActivated = ContextThemeWrapper(context, Theme_DeviceDefault)
+                .run {
+                    val typedValue = TypedValue()
+                    val a: TypedArray = obtainStyledAttributes(typedValue.data,
+                            intArrayOf(android.R.attr.colorControlActivated))
+                    val color = a.getColor(0, 0)
+                    a.recycle()
+                    color
+                }
+
+        val colorPrimary =
+                Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+
+        arrowPaint.color = Utils.getColorAccentDefaultColor(context)
+
+        arrowBackgroundPaint.color = if (isDeviceInNightTheme) {
+            colorPrimary
+        } else {
+            colorControlActivated
+        }
     }
 
-    private inner class AnimatedFloat(name: String, springForce: SpringForce) {
+    inner class AnimatedFloat(
+            name: String,
+            private val minimumVisibleChange: Float? = null,
+            private val minimumValue: Float? = null,
+            private val maximumValue: Float? = null,
+    ) {
+
         // The resting position when not stretched by a touch drag
         private var restingPosition = 0f
 
         // The current position as updated by the SpringAnimation
         var pos = 0f
-            set(v) {
+            private set(v) {
                 if (field != v) {
                     field = v
                     invalidate()
                 }
             }
 
-        val animation: SpringAnimation
+        private val animation: SpringAnimation
+        var spring: SpringForce
+            get() = animation.spring
+            set(value) {
+                animation.cancel()
+                animation.spring = value
+            }
+
+        val isRunning: Boolean
+            get() = animation.isRunning
+
+        fun addEndListener(listener: DelayedOnAnimationEndListener) {
+            animation.addEndListener(listener)
+        }
 
         init {
             val floatProp = object : FloatPropertyCompat<AnimatedFloat>(name) {
@@ -167,8 +223,12 @@
 
                 override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos
             }
-            animation = SpringAnimation(this, floatProp)
-            animation.spring = springForce
+            animation = SpringAnimation(this, floatProp).apply {
+                spring = SpringForce()
+                this@AnimatedFloat.minimumValue?.let { setMinValue(it) }
+                this@AnimatedFloat.maximumValue?.let { setMaxValue(it) }
+                this@AnimatedFloat.minimumVisibleChange?.let { minimumVisibleChange = it }
+            }
         }
 
         fun snapTo(newPosition: Float) {
@@ -178,8 +238,24 @@
             pos = newPosition
         }
 
-        fun stretchTo(stretchAmount: Float) {
-            animation.animateToFinalPosition(restingPosition + stretchAmount)
+        fun snapToRestingPosition() {
+            snapTo(restingPosition)
+        }
+
+
+        fun stretchTo(
+                stretchAmount: Float,
+                startingVelocity: Float? = null,
+                springForce: SpringForce? = null
+        ) {
+            animation.apply {
+                startingVelocity?.let {
+                    cancel()
+                    setStartVelocity(it)
+                }
+                springForce?.let { spring = springForce }
+                animateToFinalPosition(restingPosition + stretchAmount)
+            }
         }
 
         /**
@@ -188,18 +264,23 @@
          *
          * The [restingPosition] will remain unchanged. Only the animation is updated.
          */
-        fun stretchBy(finalPosition: Float, amount: Float) {
-            val stretchedAmount = amount * (finalPosition - restingPosition)
+        fun stretchBy(finalPosition: Float?, amount: Float) {
+            val stretchedAmount = amount * ((finalPosition ?: 0f) - restingPosition)
             animation.animateToFinalPosition(restingPosition + stretchedAmount)
         }
 
-        fun updateRestingPosition(pos: Float, animated: Boolean) {
+        fun updateRestingPosition(pos: Float?, animated: Boolean = true) {
+            if (pos == null) return
+
             restingPosition = pos
-            if (animated)
+            if (animated) {
                 animation.animateToFinalPosition(restingPosition)
-            else
+            } else {
                 snapTo(restingPosition)
+            }
         }
+
+        fun cancel() = animation.cancel()
     }
 
     init {
@@ -224,126 +305,203 @@
         return arrowPath
     }
 
-    fun addEndListener(endListener: DelayedOnAnimationEndListener): Boolean {
-        return if (alphaAnimation.isRunning) {
-            alphaAnimation.addEndListener(endListener)
-            true
-        } else if (horizontalTranslation.animation.isRunning) {
-            horizontalTranslation.animation.addEndListener(endListener)
+    fun addAnimationEndListener(
+            animatedFloat: AnimatedFloat,
+            endListener: DelayedOnAnimationEndListener
+    ): Boolean {
+        return if (animatedFloat.isRunning) {
+            animatedFloat.addEndListener(endListener)
             true
         } else {
-            endListener.runNow()
+            endListener.run()
             false
         }
     }
 
+    fun cancelAnimations() {
+        allAnimatedFloat.forEach { it.cancel() }
+    }
+
     fun setStretch(
-        horizontalTranslationStretchAmount: Float,
-        arrowStretchAmount: Float,
-        backgroundWidthStretchAmount: Float,
-        fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
+            horizontalTranslationStretchAmount: Float,
+            arrowStretchAmount: Float,
+            arrowAlphaStretchAmount: Float,
+            backgroundAlphaStretchAmount: Float,
+            backgroundWidthStretchAmount: Float,
+            backgroundHeightStretchAmount: Float,
+            edgeCornerStretchAmount: Float,
+            farCornerStretchAmount: Float,
+            fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
     ) {
         horizontalTranslation.stretchBy(
-            finalPosition = fullyStretchedDimens.horizontalTranslation,
-            amount = horizontalTranslationStretchAmount
+                finalPosition = fullyStretchedDimens.horizontalTranslation,
+                amount = horizontalTranslationStretchAmount
         )
         arrowLength.stretchBy(
-            finalPosition = fullyStretchedDimens.arrowDimens.length,
-            amount = arrowStretchAmount
+                finalPosition = fullyStretchedDimens.arrowDimens.length,
+                amount = arrowStretchAmount
         )
         arrowHeight.stretchBy(
-            finalPosition = fullyStretchedDimens.arrowDimens.height,
-            amount = arrowStretchAmount
+                finalPosition = fullyStretchedDimens.arrowDimens.height,
+                amount = arrowStretchAmount
+        )
+        arrowAlpha.stretchBy(
+                finalPosition = fullyStretchedDimens.arrowDimens.alpha,
+                amount = arrowAlphaStretchAmount
+        )
+        backgroundAlpha.stretchBy(
+                finalPosition = fullyStretchedDimens.backgroundDimens.alpha,
+                amount = backgroundAlphaStretchAmount
         )
         backgroundWidth.stretchBy(
-            finalPosition = fullyStretchedDimens.backgroundDimens.width,
-            amount = backgroundWidthStretchAmount
+                finalPosition = fullyStretchedDimens.backgroundDimens.width,
+                amount = backgroundWidthStretchAmount
+        )
+        backgroundHeight.stretchBy(
+                finalPosition = fullyStretchedDimens.backgroundDimens.height,
+                amount = backgroundHeightStretchAmount
+        )
+        backgroundEdgeCornerRadius.stretchBy(
+                finalPosition = fullyStretchedDimens.backgroundDimens.edgeCornerRadius,
+                amount = edgeCornerStretchAmount
+        )
+        backgroundFarCornerRadius.stretchBy(
+                finalPosition = fullyStretchedDimens.backgroundDimens.farCornerRadius,
+                amount = farCornerStretchAmount
         )
     }
 
+    fun popOffEdge(startingVelocity: Float) {
+        val heightStretchAmount = startingVelocity * 50
+        val widthStretchAmount = startingVelocity * 150
+        val scaleStretchAmount = startingVelocity * 0.8f
+        backgroundHeight.stretchTo(stretchAmount = 0f, startingVelocity = -heightStretchAmount)
+        backgroundWidth.stretchTo(stretchAmount = 0f, startingVelocity = widthStretchAmount)
+        scale.stretchTo(stretchAmount = 0f, startingVelocity = -scaleStretchAmount)
+    }
+
+    fun popScale(startingVelocity: Float) {
+        scalePivotX.snapTo(backgroundWidth.pos / 2)
+        scale.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity)
+    }
+
+    fun popArrowAlpha(startingVelocity: Float, springForce: SpringForce? = null) {
+        arrowAlpha.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity,
+                springForce = springForce)
+    }
+
     fun resetStretch() {
-        horizontalTranslation.stretchTo(0f)
-        arrowLength.stretchTo(0f)
-        arrowHeight.stretchTo(0f)
-        backgroundWidth.stretchTo(0f)
-        backgroundHeight.stretchTo(0f)
-        backgroundEdgeCornerRadius.stretchTo(0f)
-        backgroundFarCornerRadius.stretchTo(0f)
+        backgroundAlpha.snapTo(1f)
+        verticalTranslation.snapTo(0f)
+        scale.snapTo(1f)
+
+        horizontalTranslation.snapToRestingPosition()
+        arrowLength.snapToRestingPosition()
+        arrowHeight.snapToRestingPosition()
+        arrowAlpha.snapToRestingPosition()
+        backgroundWidth.snapToRestingPosition()
+        backgroundHeight.snapToRestingPosition()
+        backgroundEdgeCornerRadius.snapToRestingPosition()
+        backgroundFarCornerRadius.snapToRestingPosition()
     }
 
     /**
      * Updates resting arrow and background size not accounting for stretch
      */
     internal fun setRestingDimens(
-        restingParams: EdgePanelParams.BackIndicatorDimens,
-        animate: Boolean
+            restingParams: EdgePanelParams.BackIndicatorDimens,
+            animate: Boolean = true
     ) {
-        horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation, animate)
+        horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation)
+        scale.updateRestingPosition(restingParams.scale)
+        arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha)
+        backgroundAlpha.updateRestingPosition(restingParams.backgroundDimens.alpha)
+
         arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate)
         arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate)
+        scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate)
         backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate)
         backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate)
         backgroundEdgeCornerRadius.updateRestingPosition(
-            restingParams.backgroundDimens.edgeCornerRadius,
-            animate
+                restingParams.backgroundDimens.edgeCornerRadius, animate
         )
         backgroundFarCornerRadius.updateRestingPosition(
-            restingParams.backgroundDimens.farCornerRadius,
-            animate
+                restingParams.backgroundDimens.farCornerRadius, animate
         )
     }
 
     fun animateVertically(yPos: Float) = verticalTranslation.stretchTo(yPos)
 
-    fun setArrowStiffness(arrowStiffness: Float, arrowDampingRatio: Float) {
-        arrowLength.animation.spring.apply {
-            stiffness = arrowStiffness
-            dampingRatio = arrowDampingRatio
-        }
-        arrowHeight.animation.spring.apply {
-            stiffness = arrowStiffness
-            dampingRatio = arrowDampingRatio
-        }
+    fun setSpring(
+            horizontalTranslation: SpringForce? = null,
+            verticalTranslation: SpringForce? = null,
+            scale: SpringForce? = null,
+            arrowLength: SpringForce? = null,
+            arrowHeight: SpringForce? = null,
+            arrowAlpha: SpringForce? = null,
+            backgroundAlpha: SpringForce? = null,
+            backgroundFarCornerRadius: SpringForce? = null,
+            backgroundEdgeCornerRadius: SpringForce? = null,
+            backgroundWidth: SpringForce? = null,
+            backgroundHeight: SpringForce? = null,
+    ) {
+        arrowLength?.let { this.arrowLength.spring = it }
+        arrowHeight?.let { this.arrowHeight.spring = it }
+        arrowAlpha?.let { this.arrowAlpha.spring = it }
+        backgroundAlpha?.let { this.backgroundAlpha.spring = it }
+        backgroundFarCornerRadius?.let { this.backgroundFarCornerRadius.spring = it }
+        backgroundEdgeCornerRadius?.let { this.backgroundEdgeCornerRadius.spring = it }
+        scale?.let { this.scale.spring = it }
+        backgroundWidth?.let { this.backgroundWidth.spring = it }
+        backgroundHeight?.let { this.backgroundHeight.spring = it }
+        horizontalTranslation?.let { this.horizontalTranslation.spring = it }
+        verticalTranslation?.let { this.verticalTranslation.spring = it }
     }
 
     override fun hasOverlappingRendering() = false
 
     override fun onDraw(canvas: Canvas) {
-        var edgeCorner = backgroundEdgeCornerRadius.pos
+        val edgeCorner = backgroundEdgeCornerRadius.pos
         val farCorner = backgroundFarCornerRadius.pos
         val halfHeight = backgroundHeight.pos / 2
+        val canvasWidth = width
+        val backgroundWidth = backgroundWidth.pos
+        val scalePivotX = scalePivotX.pos
 
         canvas.save()
 
-        if (!isLeftPanel) canvas.scale(-1f, 1f, width / 2.0f, 0f)
+        if (!isLeftPanel) canvas.scale(-1f, 1f, canvasWidth / 2.0f, 0f)
 
         canvas.translate(
-            horizontalTranslation.pos,
-            height * 0.5f + verticalTranslation.pos
+                horizontalTranslation.pos,
+                height * 0.5f + verticalTranslation.pos
         )
 
+        canvas.scale(scale.pos, scale.pos, scalePivotX, 0f)
+
         val arrowBackground = arrowBackgroundRect.apply {
             left = 0f
             top = -halfHeight
-            right = backgroundWidth.pos
+            right = backgroundWidth
             bottom = halfHeight
         }.toPathWithRoundCorners(
-            topLeft = edgeCorner,
-            bottomLeft = edgeCorner,
-            topRight = farCorner,
-            bottomRight = farCorner
+                topLeft = edgeCorner,
+                bottomLeft = edgeCorner,
+                topRight = farCorner,
+                bottomRight = farCorner
         )
-        canvas.drawPath(arrowBackground, arrowBackgroundPaint)
+        canvas.drawPath(arrowBackground,
+                arrowBackgroundPaint.apply { alpha = (255 * backgroundAlpha.pos).toInt() })
 
         val dx = arrowLength.pos
         val dy = arrowHeight.pos
 
         // How far the arrow bounding box should be from the edge of the screen. Measured from
         // either the tip or the back of the arrow, whichever is closer
-        var arrowOffset = (backgroundWidth.pos - dx) / 2
+        val arrowOffset = (backgroundWidth - dx) / 2
         canvas.translate(
-            /* dx= */ arrowOffset,
-            /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
+                /* dx= */ arrowOffset,
+                /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
         )
 
         val arrowPointsAwayFromEdge = !arrowsPointLeft.xor(isLeftPanel)
@@ -355,6 +513,8 @@
         }
 
         val arrowPath = calculateArrowPath(dx = dx, dy = dy)
+        val arrowPaint = arrowPaint
+                .apply { alpha = (255 * min(arrowAlpha.pos, backgroundAlpha.pos)).toInt() }
         canvas.drawPath(arrowPath, arrowPaint)
         canvas.restore()
 
@@ -372,26 +532,17 @@
     }
 
     private fun RectF.toPathWithRoundCorners(
-        topLeft: Float = 0f,
-        topRight: Float = 0f,
-        bottomRight: Float = 0f,
-        bottomLeft: Float = 0f
+            topLeft: Float = 0f,
+            topRight: Float = 0f,
+            bottomRight: Float = 0f,
+            bottomLeft: Float = 0f
     ): Path = Path().apply {
         val corners = floatArrayOf(
-            topLeft, topLeft,
-            topRight, topRight,
-            bottomRight, bottomRight,
-            bottomLeft, bottomLeft
+                topLeft, topLeft,
+                topRight, topRight,
+                bottomRight, bottomRight,
+                bottomLeft, bottomLeft
         )
         addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW)
     }
-
-    fun cancelAlphaAnimations() {
-        alphaAnimation.cancel()
-        alpha = 1f
-    }
-
-    fun fadeOut() {
-        alphaAnimation.animateToFinalPosition(0f)
-    }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 6e927b0..367d125 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -24,18 +24,16 @@
 import android.os.SystemClock
 import android.os.VibrationEffect
 import android.util.Log
-import android.util.MathUtils.constrain
-import android.util.MathUtils.saturate
+import android.util.MathUtils
 import android.view.Gravity
 import android.view.MotionEvent
 import android.view.VelocityTracker
-import android.view.View
 import android.view.ViewConfiguration
 import android.view.WindowManager
-import android.view.animation.DecelerateInterpolator
-import android.view.animation.PathInterpolator
+import androidx.annotation.VisibleForTesting
+import androidx.core.os.postDelayed
+import androidx.core.view.isVisible
 import androidx.dynamicanimation.animation.DynamicAnimation
-import androidx.dynamicanimation.animation.SpringForce
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -50,58 +48,42 @@
 import kotlin.math.sign
 
 private const val TAG = "BackPanelController"
-private const val DEBUG = false
-
 private const val ENABLE_FAILSAFE = true
 
-private const val FAILSAFE_DELAY_MS: Long = 350
+private const val PX_PER_SEC = 1000
+private const val PX_PER_MS = 1
 
-/**
- * The time required between the arrow-appears vibration effect and the back-committed vibration
- * effect. If the arrow is flung quickly, the phone only vibrates once. However, if the arrow is
- * held on the screen for a long time, it will vibrate a second time when the back gesture is
- * committed.
- */
-private const val GESTURE_DURATION_FOR_CLICK_MS = 400
+internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L
+private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
+private const val MIN_DURATION_COMMITTED_ANIMATION = 200L
+private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
+private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L
 
-/**
- * The min duration arrow remains on screen during a fling event.
- */
-private const val FLING_MIN_APPEARANCE_DURATION = 235L
+private const val FAILSAFE_DELAY_MS = 350L
+private const val POP_ON_FLING_DELAY = 160L
 
-/**
- * The min duration arrow remains on screen during a fling event.
- */
-private const val MIN_FLING_VELOCITY = 3000
+internal val VIBRATE_ACTIVATED_EFFECT =
+        VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
 
-/**
- * The amount of rubber banding we do for the vertical translation
- */
-private const val RUBBER_BAND_AMOUNT = 15
+internal val VIBRATE_DEACTIVATED_EFFECT =
+        VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
 
-private const val ARROW_APPEAR_STIFFNESS = 600f
-private const val ARROW_APPEAR_DAMPING_RATIO = 0.4f
-private const val ARROW_DISAPPEAR_STIFFNESS = 1200f
-private const val ARROW_DISAPPEAR_DAMPING_RATIO = SpringForce.DAMPING_RATIO_NO_BOUNCY
+private const val DEBUG = false
 
-/**
- * The interpolator used to rubber band
- */
-private val RUBBER_BAND_INTERPOLATOR = PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f)
-
-private val DECELERATE_INTERPOLATOR = DecelerateInterpolator()
-
-private val DECELERATE_INTERPOLATOR_SLOW = DecelerateInterpolator(0.7f)
-
-class BackPanelController private constructor(
-    context: Context,
-    private val windowManager: WindowManager,
-    private val viewConfiguration: ViewConfiguration,
-    @Main private val mainHandler: Handler,
-    private val vibratorHelper: VibratorHelper,
-    private val configurationController: ConfigurationController,
-    latencyTracker: LatencyTracker
-) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
+class BackPanelController internal constructor(
+        context: Context,
+        private val windowManager: WindowManager,
+        private val viewConfiguration: ViewConfiguration,
+        @Main private val mainHandler: Handler,
+        private val vibratorHelper: VibratorHelper,
+        private val configurationController: ConfigurationController,
+        private val latencyTracker: LatencyTracker
+) : ViewController<BackPanel>(
+        BackPanel(
+                context,
+                latencyTracker
+        )
+), NavigationEdgeBackPlugin {
 
     /**
      * Injectable instance to create a new BackPanelController.
@@ -110,44 +92,44 @@
      * BackPanelController, and we need to match EdgeBackGestureHandler's context.
      */
     class Factory @Inject constructor(
-        private val windowManager: WindowManager,
-        private val viewConfiguration: ViewConfiguration,
-        @Main private val mainHandler: Handler,
-        private val vibratorHelper: VibratorHelper,
-        private val configurationController: ConfigurationController,
-        private val latencyTracker: LatencyTracker
+            private val windowManager: WindowManager,
+            private val viewConfiguration: ViewConfiguration,
+            @Main private val mainHandler: Handler,
+            private val vibratorHelper: VibratorHelper,
+            private val configurationController: ConfigurationController,
+            private val latencyTracker: LatencyTracker
     ) {
         /** Construct a [BackPanelController].  */
         fun create(context: Context): BackPanelController {
             val backPanelController = BackPanelController(
-                context,
-                windowManager,
-                viewConfiguration,
-                mainHandler,
-                vibratorHelper,
-                configurationController,
-                latencyTracker
+                    context,
+                    windowManager,
+                    viewConfiguration,
+                    mainHandler,
+                    vibratorHelper,
+                    configurationController,
+                    latencyTracker
             )
             backPanelController.init()
             return backPanelController
         }
     }
 
-    private var params: EdgePanelParams = EdgePanelParams(resources)
-    private var currentState: GestureState = GestureState.GONE
+    @VisibleForTesting
+    internal var params: EdgePanelParams = EdgePanelParams(resources)
+    @VisibleForTesting
+    internal var currentState: GestureState = GestureState.GONE
     private var previousState: GestureState = GestureState.GONE
 
-    // Phone should only vibrate the first time the arrow is activated
-    private var hasHapticPlayed = false
-
     // Screen attributes
     private lateinit var layoutParams: WindowManager.LayoutParams
     private val displaySize = Point()
 
     private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
-
+    private var previousXTranslationOnActiveOffset = 0f
     private var previousXTranslation = 0f
     private var totalTouchDelta = 0f
+    private var touchDeltaStartX = 0f
     private var velocityTracker: VelocityTracker? = null
         set(value) {
             if (field != value) field?.recycle()
@@ -161,8 +143,18 @@
     // The x,y position of the first touch event
     private var startX = 0f
     private var startY = 0f
+    private var startIsLeft: Boolean? = null
 
-    private var gestureStartTime = 0L
+    private var gestureSinceActionDown = 0L
+    private var gestureEntryTime = 0L
+    private var gestureActiveTime = 0L
+    private var gestureInactiveOrEntryTime = 0L
+    private var gestureArrowStrokeVisibleTime = 0L
+
+    private val elapsedTimeSinceActionDown
+        get() = SystemClock.uptimeMillis() - gestureSinceActionDown
+    private val elapsedTimeSinceEntry
+        get() = SystemClock.uptimeMillis() - gestureEntryTime
 
     // Whether the current gesture has moved a sufficiently large amount,
     // so that we can unambiguously start showing the ENTRY animation
@@ -170,7 +162,7 @@
 
     private val failsafeRunnable = Runnable { onFailsafe() }
 
-    private enum class GestureState {
+    internal enum class GestureState {
         /* Arrow is off the screen and invisible */
         GONE,
 
@@ -191,17 +183,6 @@
 
         /* back action currently cancelling, arrow soon to be GONE */
         CANCELLED;
-
-        /**
-         * @return true if the current state responds to touch move events in some way (e.g. by
-         * stretching the back indicator)
-         */
-        fun isInteractive(): Boolean {
-            return when (this) {
-                ENTRY, ACTIVE, INACTIVE -> true
-                GONE, FLUNG, COMMITTED, CANCELLED -> false
-            }
-        }
     }
 
     /**
@@ -209,50 +190,43 @@
      * runnable is not called if the animation is cancelled
      */
     inner class DelayedOnAnimationEndListener internal constructor(
-        private val handler: Handler,
-        private val runnable: Runnable,
-        private val minDuration: Long
+            private val handler: Handler,
+            private val runnableDelay: Long,
+            val runnable: Runnable,
     ) : DynamicAnimation.OnAnimationEndListener {
+
         override fun onAnimationEnd(
-            animation: DynamicAnimation<*>,
-            canceled: Boolean,
-            value: Float,
-            velocity: Float
+                animation: DynamicAnimation<*>,
+                canceled: Boolean,
+                value: Float,
+                velocity: Float
         ) {
             animation.removeEndListener(this)
+
             if (!canceled) {
-                // Total elapsed time of the gesture and the animation
-                val totalElapsedTime = SystemClock.uptimeMillis() - gestureStartTime
+
                 // The delay between finishing this animation and starting the runnable
-                val delay = max(0, minDuration - totalElapsedTime)
+                val delay = max(0, runnableDelay - elapsedTimeSinceEntry)
+
                 handler.postDelayed(runnable, delay)
             }
         }
 
-        internal fun runNow() {
-            runnable.run()
-        }
+        internal fun run() = runnable.run()
     }
 
-    private val setCommittedEndListener =
-        DelayedOnAnimationEndListener(
-            mainHandler,
-            { updateArrowState(GestureState.COMMITTED) },
-            minDuration = FLING_MIN_APPEARANCE_DURATION
-        )
+    private val onEndSetCommittedStateListener = DelayedOnAnimationEndListener(mainHandler, 0L) {
+        updateArrowState(GestureState.COMMITTED)
+    }
 
-    private val setGoneEndListener =
-        DelayedOnAnimationEndListener(
-            mainHandler,
-            {
+
+    private val onEndSetGoneStateListener =
+            DelayedOnAnimationEndListener(mainHandler, runnableDelay = 0L) {
                 cancelFailsafe()
                 updateArrowState(GestureState.GONE)
-            },
-            minDuration = 0
-        )
+            }
 
-    // Vibration
-    private var vibrationTime: Long = 0
+    private val playAnimationThenSetGoneOnAlphaEnd = Runnable { playAnimationThenSetGoneEnd() }
 
     // Minimum of the screen's width or the predefined threshold
     private var fullyStretchedThreshold = 0f
@@ -279,7 +253,7 @@
         updateConfiguration()
         updateArrowDirection(configurationController.isLayoutRtl)
         updateArrowState(GestureState.GONE, force = true)
-        updateRestingArrowDimens(animated = false, currentState)
+        updateRestingArrowDimens()
         configurationController.addCallback(configurationListener)
     }
 
@@ -296,22 +270,57 @@
         velocityTracker!!.addMovement(event)
         when (event.actionMasked) {
             MotionEvent.ACTION_DOWN -> {
-                resetOnDown()
+                gestureSinceActionDown = SystemClock.uptimeMillis()
+                cancelAllPendingAnimations()
                 startX = event.x
                 startY = event.y
-                gestureStartTime = SystemClock.uptimeMillis()
+
+                updateArrowState(GestureState.GONE)
+                updateYStartPosition(startY)
+
+                // reset animation properties
+                startIsLeft = mView.isLeftPanel
+                hasPassedDragSlop = false
+                mView.resetStretch()
             }
             MotionEvent.ACTION_MOVE -> {
-                // only go to the ENTRY state after some minimum motion has occurred
                 if (dragSlopExceeded(event.x, startX)) {
                     handleMoveEvent(event)
                 }
             }
             MotionEvent.ACTION_UP -> {
-                if (currentState == GestureState.ACTIVE) {
-                    updateArrowState(if (isFlung()) GestureState.FLUNG else GestureState.COMMITTED)
-                } else if (currentState != GestureState.GONE) { // if invisible, skip animation
-                    updateArrowState(GestureState.CANCELLED)
+                when (currentState) {
+                    GestureState.ENTRY -> {
+                        if (isFlungAwayFromEdge(endX = event.x)) {
+                            updateArrowState(GestureState.ACTIVE)
+                            updateArrowState(GestureState.FLUNG)
+                        } else {
+                            updateArrowState(GestureState.CANCELLED)
+                        }
+                    }
+                    GestureState.INACTIVE -> {
+                        if (isFlungAwayFromEdge(endX = event.x)) {
+                            mainHandler.postDelayed(MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION) {
+                                updateArrowState(GestureState.ACTIVE)
+                                updateArrowState(GestureState.FLUNG)
+                            }
+                        } else {
+                            updateArrowState(GestureState.CANCELLED)
+                        }
+                    }
+                    GestureState.ACTIVE -> {
+                        if (elapsedTimeSinceEntry < MIN_DURATION_CONSIDERED_AS_FLING) {
+                            updateArrowState(GestureState.FLUNG)
+                        } else {
+                            updateArrowState(GestureState.COMMITTED)
+                        }
+                    }
+                    GestureState.GONE,
+                    GestureState.FLUNG,
+                    GestureState.COMMITTED,
+                    GestureState.CANCELLED -> {
+                        updateArrowState(GestureState.CANCELLED)
+                    }
                 }
                 velocityTracker = null
             }
@@ -325,6 +334,14 @@
         }
     }
 
+    private fun cancelAllPendingAnimations() {
+        cancelFailsafe()
+        mView.cancelAnimations()
+        mainHandler.removeCallbacks(onEndSetCommittedStateListener.runnable)
+        mainHandler.removeCallbacks(onEndSetGoneStateListener.runnable)
+        mainHandler.removeCallbacks(playAnimationThenSetGoneOnAlphaEnd)
+    }
+
     /**
      * Returns false until the current gesture exceeds the touch slop threshold,
      * and returns true thereafter (we reset on the subsequent back gesture).
@@ -335,7 +352,7 @@
     private fun dragSlopExceeded(curX: Float, startX: Float): Boolean {
         if (hasPassedDragSlop) return true
 
-        if (abs(curX - startX) > viewConfiguration.scaledTouchSlop) {
+        if (abs(curX - startX) > viewConfiguration.scaledEdgeSlop) {
             // Reset the arrow to the side
             updateArrowState(GestureState.ENTRY)
 
@@ -348,39 +365,46 @@
     }
 
     private fun updateArrowStateOnMove(yTranslation: Float, xTranslation: Float) {
-        if (!currentState.isInteractive())
-            return
+
+        val isWithinYActivationThreshold = xTranslation * 2 >= yTranslation
 
         when (currentState) {
-            // Check if we should transition from ENTRY to ACTIVE
-            GestureState.ENTRY ->
-                if (xTranslation > params.swipeTriggerThreshold) {
+            GestureState.ENTRY -> {
+                if (xTranslation > params.staticTriggerThreshold) {
                     updateArrowState(GestureState.ACTIVE)
                 }
+            }
+            GestureState.ACTIVE -> {
+                val isPastDynamicDeactivationThreshold =
+                        totalTouchDelta <= params.deactivationSwipeTriggerThreshold
+                val isMinDurationElapsed =
+                        elapsedTimeSinceActionDown > MIN_DURATION_ACTIVE_ANIMATION
 
-            // Abort if we had continuous motion toward the edge for a while, OR the direction
-            // in Y is bigger than X * 2
-            GestureState.ACTIVE ->
-                if ((totalTouchDelta < 0 && -totalTouchDelta > params.minDeltaForSwitch) ||
-                    (yTranslation > xTranslation * 2)
+                if (isMinDurationElapsed && (!isWithinYActivationThreshold ||
+                                isPastDynamicDeactivationThreshold)
                 ) {
                     updateArrowState(GestureState.INACTIVE)
                 }
+            }
+            GestureState.INACTIVE -> {
+                val isPastStaticThreshold =
+                        xTranslation > params.staticTriggerThreshold
+                val isPastDynamicReactivationThreshold = totalTouchDelta > 0 &&
+                        abs(totalTouchDelta) >=
+                        params.reactivationTriggerThreshold
 
-            //  Re-activate if we had continuous motion away from the edge for a while
-            GestureState.INACTIVE ->
-                if (totalTouchDelta > 0 && totalTouchDelta > params.minDeltaForSwitch) {
+                if (isPastStaticThreshold &&
+                        isPastDynamicReactivationThreshold &&
+                        isWithinYActivationThreshold
+                ) {
                     updateArrowState(GestureState.ACTIVE)
                 }
-
-            // By default assume the current direction is kept
+            }
             else -> {}
         }
     }
 
     private fun handleMoveEvent(event: MotionEvent) {
-        if (!currentState.isInteractive())
-            return
 
         val x = event.x
         val y = event.y
@@ -400,23 +424,44 @@
         previousXTranslation = xTranslation
 
         if (abs(xDelta) > 0) {
-            if (sign(xDelta) == sign(totalTouchDelta)) {
+            val range =
+                params.run { deactivationSwipeTriggerThreshold..reactivationTriggerThreshold }
+            val isTouchInContinuousDirection =
+                    sign(xDelta) == sign(totalTouchDelta) || totalTouchDelta in range
+
+            if (isTouchInContinuousDirection) {
                 // Direction has NOT changed, so keep counting the delta
                 totalTouchDelta += xDelta
             } else {
                 // Direction has changed, so reset the delta
                 totalTouchDelta = xDelta
+                touchDeltaStartX = x
             }
         }
 
         updateArrowStateOnMove(yTranslation, xTranslation)
+
         when (currentState) {
-            GestureState.ACTIVE ->
-                stretchActiveBackIndicator(fullScreenStretchProgress(xTranslation))
-            GestureState.ENTRY ->
-                stretchEntryBackIndicator(preThresholdStretchProgress(xTranslation))
-            GestureState.INACTIVE ->
-                mView.resetStretch()
+            GestureState.ACTIVE -> {
+                stretchActiveBackIndicator(fullScreenProgress(xTranslation))
+            }
+            GestureState.ENTRY -> {
+                val progress = staticThresholdProgress(xTranslation)
+                stretchEntryBackIndicator(progress)
+
+                params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+                    mView.popArrowAlpha(0f, it.value)
+                }
+            }
+            GestureState.INACTIVE -> {
+                val progress = reactivationThresholdProgress(totalTouchDelta)
+                stretchInactiveBackIndicator(progress)
+
+                params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+                    gestureArrowStrokeVisibleTime = SystemClock.uptimeMillis()
+                    mView.popArrowAlpha(0f, it.value)
+                }
+            }
             else -> {}
         }
 
@@ -427,21 +472,22 @@
     private fun setVerticalTranslation(yOffset: Float) {
         val yTranslation = abs(yOffset)
         val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f
-        val yProgress = saturate(yTranslation / (maxYOffset * RUBBER_BAND_AMOUNT))
-        mView.animateVertically(
-            RUBBER_BAND_INTERPOLATOR.getInterpolation(yProgress) * maxYOffset *
+        val rubberbandAmount = 15f
+        val yProgress = MathUtils.saturate(yTranslation / (maxYOffset * rubberbandAmount))
+        val yPosition = params.translationInterpolator.getInterpolation(yProgress) *
+                maxYOffset *
                 sign(yOffset)
-        )
+        mView.animateVertically(yPosition)
     }
 
     /**
-     * @return the relative position of the drag from the time after the arrow is activated until
+     * Tracks the relative position of the drag from the time after the arrow is activated until
      * the arrow is fully stretched (between 0.0 - 1.0f)
      */
-    private fun fullScreenStretchProgress(xTranslation: Float): Float {
-        return saturate(
-            (xTranslation - params.swipeTriggerThreshold) /
-                (fullyStretchedThreshold - params.swipeTriggerThreshold)
+    private fun fullScreenProgress(xTranslation: Float): Float {
+        return MathUtils.saturate(
+                (xTranslation - previousXTranslationOnActiveOffset) /
+                        (fullyStretchedThreshold - previousXTranslationOnActiveOffset)
         )
     }
 
@@ -449,26 +495,74 @@
      * Tracks the relative position of the drag from the entry until the threshold where the arrow
      * activates (between 0.0 - 1.0f)
      */
-    private fun preThresholdStretchProgress(xTranslation: Float): Float {
-        return saturate(xTranslation / params.swipeTriggerThreshold)
+    private fun staticThresholdProgress(xTranslation: Float): Float {
+        return MathUtils.saturate(xTranslation / params.staticTriggerThreshold)
+    }
+
+    private fun reactivationThresholdProgress(totalTouchDelta: Float): Float {
+        return MathUtils.saturate(totalTouchDelta / params.reactivationTriggerThreshold)
     }
 
     private fun stretchActiveBackIndicator(progress: Float) {
-        val rubberBandIterpolation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
         mView.setStretch(
-            horizontalTranslationStretchAmount = rubberBandIterpolation,
-            arrowStretchAmount = rubberBandIterpolation,
-            backgroundWidthStretchAmount = DECELERATE_INTERPOLATOR_SLOW.getInterpolation(progress),
-            params.fullyStretchedIndicator
+                horizontalTranslationStretchAmount = params.translationInterpolator
+                        .getInterpolation(progress),
+                arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
+                backgroundWidthStretchAmount = params.activeWidthInterpolator
+                        .getInterpolation(progress),
+                backgroundAlphaStretchAmount = 1f,
+                backgroundHeightStretchAmount = 1f,
+                arrowAlphaStretchAmount = 1f,
+                edgeCornerStretchAmount = 1f,
+                farCornerStretchAmount = 1f,
+                fullyStretchedDimens = params.fullyStretchedIndicator
         )
     }
 
     private fun stretchEntryBackIndicator(progress: Float) {
         mView.setStretch(
-            horizontalTranslationStretchAmount = 0f,
-            arrowStretchAmount = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress),
-            backgroundWidthStretchAmount = DECELERATE_INTERPOLATOR.getInterpolation(progress),
-            params.preThresholdIndicator
+                horizontalTranslationStretchAmount = 0f,
+                arrowStretchAmount = params.arrowAngleInterpolator
+                        .getInterpolation(progress),
+                backgroundWidthStretchAmount = params.entryWidthInterpolator
+                        .getInterpolation(progress),
+                backgroundHeightStretchAmount = params.heightInterpolator
+                        .getInterpolation(progress),
+                backgroundAlphaStretchAmount = 1f,
+                arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value,
+                edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
+                farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
+                fullyStretchedDimens = params.preThresholdIndicator
+        )
+    }
+
+    private var previousPreThresholdWidthInterpolator = params.entryWidthTowardsEdgeInterpolator
+    fun preThresholdWidthStretchAmount(progress: Float): Float {
+        val interpolator = run {
+            val isPastSlop = abs(totalTouchDelta) > ViewConfiguration.get(context).scaledTouchSlop
+            if (isPastSlop) {
+                if (totalTouchDelta > 0) {
+                    params.entryWidthInterpolator
+                } else params.entryWidthTowardsEdgeInterpolator
+            } else {
+                previousPreThresholdWidthInterpolator
+            }.also { previousPreThresholdWidthInterpolator = it }
+        }
+        return interpolator.getInterpolation(progress).coerceAtLeast(0f)
+    }
+
+    private fun stretchInactiveBackIndicator(progress: Float) {
+        mView.setStretch(
+                horizontalTranslationStretchAmount = 0f,
+                arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
+                backgroundWidthStretchAmount = preThresholdWidthStretchAmount(progress),
+                backgroundHeightStretchAmount = params.heightInterpolator
+                        .getInterpolation(progress),
+                backgroundAlphaStretchAmount = 1f,
+                arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value,
+                edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
+                farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
+                fullyStretchedDimens = params.preThresholdIndicator
         )
     }
 
@@ -486,8 +580,7 @@
         }
     }
 
-    override fun setInsets(insetLeft: Int, insetRight: Int) {
-    }
+    override fun setInsets(insetLeft: Int, insetRight: Int) = Unit
 
     override fun setBackCallback(callback: NavigationEdgeBackPlugin.BackCallback) {
         backCallback = callback
@@ -498,62 +591,54 @@
         windowManager.addView(mView, layoutParams)
     }
 
-    private fun isFlung() = velocityTracker!!.run {
-        computeCurrentVelocity(1000)
-        abs(xVelocity) > MIN_FLING_VELOCITY
+    private fun isDragAwayFromEdge(velocityPxPerSecThreshold: Int = 0) = velocityTracker!!.run {
+        computeCurrentVelocity(PX_PER_SEC)
+        val velocity = xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
+        velocity > velocityPxPerSecThreshold
     }
 
-    private fun playFlingBackAnimation() {
-        playAnimation(setCommittedEndListener)
+    private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean {
+        val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop
+        val flingDistance = abs(endX - startX)
+        val isPastFlingVelocity = isDragAwayFromEdge(
+                velocityPxPerSecThreshold =
+                ViewConfiguration.get(context).scaledMinimumFlingVelocity)
+        return flingDistance > minDistanceConsideredForFling && isPastFlingVelocity
     }
 
-    private fun playCommitBackAnimation() {
-        // Check if we should vibrate again
-        if (previousState != GestureState.FLUNG) {
-            velocityTracker!!.computeCurrentVelocity(1000)
-            val isSlow = abs(velocityTracker!!.xVelocity) < 500
-            val hasNotVibratedRecently =
-                SystemClock.uptimeMillis() - vibrationTime >= GESTURE_DURATION_FOR_CLICK_MS
-            if (isSlow || hasNotVibratedRecently) {
-                vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
-            }
-        }
-        // Dispatch the actual back trigger
-        if (DEBUG) Log.d(TAG, "playCommitBackAnimation() invoked triggerBack() on backCallback")
-        backCallback.triggerBack()
-
-        playAnimation(setGoneEndListener)
-    }
-
-    private fun playCancelBackAnimation() {
-        backCallback.cancelBack()
-        playAnimation(setGoneEndListener)
-    }
-
-    /**
-     * @return true if the animation is running, false otherwise. Some transitions don't animate
-     */
-    private fun playAnimation(endListener: DelayedOnAnimationEndListener) {
-        updateRestingArrowDimens(animated = true, currentState)
-
-        if (!mView.addEndListener(endListener)) {
+    private fun playHorizontalAnimationThen(onEnd: DelayedOnAnimationEndListener) {
+        updateRestingArrowDimens()
+        if (!mView.addAnimationEndListener(mView.horizontalTranslation, onEnd)) {
             scheduleFailsafe()
         }
     }
 
-    private fun resetOnDown() {
-        hasPassedDragSlop = false
-        hasHapticPlayed = false
-        totalTouchDelta = 0f
-        vibrationTime = 0
-        cancelFailsafe()
+    private fun playAnimationThenSetGoneEnd() {
+        updateRestingArrowDimens()
+        if (!mView.addAnimationEndListener(mView.backgroundAlpha, onEndSetGoneStateListener)) {
+            scheduleFailsafe()
+        }
     }
 
-    private fun updateYPosition(touchY: Float) {
+    private fun playWithBackgroundWidthAnimation(
+            onEnd: DelayedOnAnimationEndListener,
+            delay: Long = 0L
+    ) {
+        if (delay == 0L) {
+            updateRestingArrowDimens()
+            if (!mView.addAnimationEndListener(mView.backgroundWidth, onEnd)) {
+                scheduleFailsafe()
+            }
+        } else {
+            mainHandler.postDelayed(delay) { playWithBackgroundWidthAnimation(onEnd, delay = 0L) }
+        }
+    }
+
+    private fun updateYStartPosition(touchY: Float) {
         var yPosition = touchY - params.fingerOffset
         yPosition = max(yPosition, params.minArrowYPosition.toFloat())
         yPosition -= layoutParams.height / 2.0f
-        layoutParams.y = constrain(yPosition.toInt(), 0, displaySize.y)
+        layoutParams.y = MathUtils.constrain(yPosition.toInt(), 0, displaySize.y)
     }
 
     override fun setDisplaySize(displaySize: Point) {
@@ -564,53 +649,135 @@
     /**
      * Updates resting arrow and background size not accounting for stretch
      */
-    private fun updateRestingArrowDimens(animated: Boolean, currentState: GestureState) {
-        if (animated) {
-            when (currentState) {
-                GestureState.ENTRY, GestureState.ACTIVE, GestureState.FLUNG ->
-                    mView.setArrowStiffness(ARROW_APPEAR_STIFFNESS, ARROW_APPEAR_DAMPING_RATIO)
-                GestureState.CANCELLED -> mView.fadeOut()
-                else ->
-                    mView.setArrowStiffness(
-                        ARROW_DISAPPEAR_STIFFNESS,
-                        ARROW_DISAPPEAR_DAMPING_RATIO
-                    )
+    private fun updateRestingArrowDimens() {
+        when (currentState) {
+            GestureState.GONE,
+            GestureState.ENTRY -> {
+                mView.setSpring(
+                        arrowLength = params.entryIndicator.arrowDimens.lengthSpring,
+                        arrowHeight = params.entryIndicator.arrowDimens.heightSpring,
+                        arrowAlpha = params.entryIndicator.arrowDimens.alphaSpring,
+                        scale = params.entryIndicator.scaleSpring,
+                        verticalTranslation = params.entryIndicator.verticalTranslationSpring,
+                        horizontalTranslation = params.entryIndicator.horizontalTranslationSpring,
+                        backgroundAlpha = params.entryIndicator.backgroundDimens.alphaSpring,
+                        backgroundWidth = params.entryIndicator.backgroundDimens.widthSpring,
+                        backgroundHeight = params.entryIndicator.backgroundDimens.heightSpring,
+                        backgroundEdgeCornerRadius = params.entryIndicator.backgroundDimens
+                                .edgeCornerRadiusSpring,
+                        backgroundFarCornerRadius = params.entryIndicator.backgroundDimens
+                                .farCornerRadiusSpring,
+                )
             }
+            GestureState.INACTIVE -> {
+                mView.setSpring(
+                        arrowLength = params.preThresholdIndicator.arrowDimens.lengthSpring,
+                        arrowHeight = params.preThresholdIndicator.arrowDimens.heightSpring,
+                        horizontalTranslation = params.preThresholdIndicator
+                                .horizontalTranslationSpring,
+                        scale = params.preThresholdIndicator.scaleSpring,
+                        backgroundWidth = params.preThresholdIndicator.backgroundDimens
+                                .widthSpring,
+                        backgroundHeight = params.preThresholdIndicator.backgroundDimens
+                                .heightSpring,
+                        backgroundEdgeCornerRadius = params.preThresholdIndicator.backgroundDimens
+                                .edgeCornerRadiusSpring,
+                        backgroundFarCornerRadius = params.preThresholdIndicator.backgroundDimens
+                                .farCornerRadiusSpring,
+                )
+            }
+            GestureState.ACTIVE -> {
+                mView.setSpring(
+                        arrowLength = params.activeIndicator.arrowDimens.lengthSpring,
+                        arrowHeight = params.activeIndicator.arrowDimens.heightSpring,
+                        scale = params.activeIndicator.scaleSpring,
+                        horizontalTranslation = params.activeIndicator.horizontalTranslationSpring,
+                        backgroundWidth = params.activeIndicator.backgroundDimens.widthSpring,
+                        backgroundHeight = params.activeIndicator.backgroundDimens.heightSpring,
+                        backgroundEdgeCornerRadius = params.activeIndicator.backgroundDimens
+                                .edgeCornerRadiusSpring,
+                        backgroundFarCornerRadius = params.activeIndicator.backgroundDimens
+                                .farCornerRadiusSpring,
+                )
+            }
+            GestureState.FLUNG -> {
+                mView.setSpring(
+                        arrowLength = params.flungIndicator.arrowDimens.lengthSpring,
+                        arrowHeight = params.flungIndicator.arrowDimens.heightSpring,
+                        backgroundWidth = params.flungIndicator.backgroundDimens.widthSpring,
+                        backgroundHeight = params.flungIndicator.backgroundDimens.heightSpring,
+                        backgroundEdgeCornerRadius = params.flungIndicator.backgroundDimens
+                                .edgeCornerRadiusSpring,
+                        backgroundFarCornerRadius = params.flungIndicator.backgroundDimens
+                                .farCornerRadiusSpring,
+                )
+            }
+            GestureState.COMMITTED -> {
+                mView.setSpring(
+                        arrowLength = params.committedIndicator.arrowDimens.lengthSpring,
+                        arrowHeight = params.committedIndicator.arrowDimens.heightSpring,
+                        scale = params.committedIndicator.scaleSpring,
+                        backgroundWidth = params.committedIndicator.backgroundDimens.widthSpring,
+                        backgroundHeight = params.committedIndicator.backgroundDimens.heightSpring,
+                        backgroundEdgeCornerRadius = params.committedIndicator.backgroundDimens
+                                .edgeCornerRadiusSpring,
+                        backgroundFarCornerRadius = params.committedIndicator.backgroundDimens
+                                .farCornerRadiusSpring,
+                )
+            }
+            else -> {}
         }
+
         mView.setRestingDimens(
-            restingParams = EdgePanelParams.BackIndicatorDimens(
-                horizontalTranslation = when (currentState) {
-                    GestureState.GONE -> -params.activeIndicator.backgroundDimens.width
-                    // Position the committed arrow slightly further off the screen so we  do not
-                    // see part of it bouncing
-                    GestureState.COMMITTED ->
-                        -params.activeIndicator.backgroundDimens.width * 1.5f
-                    GestureState.FLUNG -> params.fullyStretchedIndicator.horizontalTranslation
-                    GestureState.ACTIVE -> params.activeIndicator.horizontalTranslation
-                    GestureState.ENTRY, GestureState.INACTIVE, GestureState.CANCELLED ->
-                        params.entryIndicator.horizontalTranslation
-                },
-                arrowDimens = when (currentState) {
-                    GestureState.ACTIVE, GestureState.INACTIVE,
-                    GestureState.COMMITTED, GestureState.FLUNG -> params.activeIndicator.arrowDimens
-                    GestureState.CANCELLED -> params.cancelledArrowDimens
-                    GestureState.GONE, GestureState.ENTRY -> params.entryIndicator.arrowDimens
-                },
-                backgroundDimens = when (currentState) {
-                    GestureState.GONE, GestureState.ENTRY -> params.entryIndicator.backgroundDimens
-                    else ->
-                        params.activeIndicator.backgroundDimens.copy(
-                            edgeCornerRadius =
-                            if (currentState == GestureState.INACTIVE ||
-                                currentState == GestureState.CANCELLED
-                            )
-                                params.cancelledEdgeCornerRadius
-                            else
-                                params.activeIndicator.backgroundDimens.edgeCornerRadius
-                        )
-                }
-            ),
-            animate = animated
+                animate = !(currentState == GestureState.FLUNG ||
+                        currentState == GestureState.COMMITTED),
+                restingParams = EdgePanelParams.BackIndicatorDimens(
+                        scale = when (currentState) {
+                            GestureState.ACTIVE,
+                            GestureState.FLUNG,
+                            -> params.activeIndicator.scale
+                            GestureState.COMMITTED -> params.committedIndicator.scale
+                            else -> params.preThresholdIndicator.scale
+                        },
+                        scalePivotX = when (currentState) {
+                            GestureState.GONE,
+                            GestureState.ENTRY,
+                            GestureState.INACTIVE,
+                            GestureState.CANCELLED -> params.preThresholdIndicator.scalePivotX
+                            else -> params.committedIndicator.scalePivotX
+                        },
+                        horizontalTranslation = when (currentState) {
+                            GestureState.GONE -> {
+                                params.activeIndicator.backgroundDimens.width?.times(-1)
+                            }
+                            GestureState.ENTRY,
+                            GestureState.INACTIVE -> params.entryIndicator.horizontalTranslation
+                            GestureState.FLUNG -> params.activeIndicator.horizontalTranslation
+                            GestureState.ACTIVE -> params.activeIndicator.horizontalTranslation
+                            GestureState.CANCELLED -> {
+                                params.cancelledIndicator.horizontalTranslation
+                            }
+                            else -> null
+                        },
+                        arrowDimens = when (currentState) {
+                            GestureState.GONE,
+                            GestureState.ENTRY,
+                            GestureState.INACTIVE -> params.entryIndicator.arrowDimens
+                            GestureState.ACTIVE -> params.activeIndicator.arrowDimens
+                            GestureState.FLUNG,
+                            GestureState.COMMITTED -> params.committedIndicator.arrowDimens
+                            GestureState.CANCELLED -> params.cancelledIndicator.arrowDimens
+                        },
+                        backgroundDimens = when (currentState) {
+                            GestureState.GONE,
+                            GestureState.ENTRY,
+                            GestureState.INACTIVE -> params.entryIndicator.backgroundDimens
+                            GestureState.ACTIVE -> params.activeIndicator.backgroundDimens
+                            GestureState.FLUNG -> params.activeIndicator.backgroundDimens
+                            GestureState.COMMITTED -> params.committedIndicator.backgroundDimens
+                            GestureState.CANCELLED -> params.cancelledIndicator.backgroundDimens
+                        }
+                )
         )
     }
 
@@ -623,42 +790,123 @@
     private fun updateArrowState(newState: GestureState, force: Boolean = false) {
         if (!force && currentState == newState) return
 
-        if (DEBUG) Log.d(TAG, "updateArrowState $currentState -> $newState")
         previousState = currentState
         currentState = newState
-        if (currentState == GestureState.GONE) {
-            mView.cancelAlphaAnimations()
-            mView.visibility = View.GONE
-        } else {
-            mView.visibility = View.VISIBLE
+
+        when (currentState) {
+            GestureState.CANCELLED -> {
+                backCallback.cancelBack()
+            }
+            GestureState.FLUNG,
+            GestureState.COMMITTED -> {
+                // When flung, trigger back immediately but don't fire again
+                // once state resolves to committed.
+                if (previousState != GestureState.FLUNG) backCallback.triggerBack()
+            }
+            GestureState.ENTRY,
+            GestureState.INACTIVE -> {
+                backCallback.setTriggerBack(false)
+            }
+            GestureState.ACTIVE -> {
+                backCallback.setTriggerBack(true)
+            }
+            GestureState.GONE -> { }
         }
 
         when (currentState) {
             // Transitioning to GONE never animates since the arrow is (presumably) already off the
             // screen
-            GestureState.GONE -> updateRestingArrowDimens(animated = false, currentState)
+            GestureState.GONE -> {
+                updateRestingArrowDimens()
+                mView.isVisible = false
+            }
             GestureState.ENTRY -> {
-                updateYPosition(startY)
-                updateRestingArrowDimens(animated = true, currentState)
+                mView.isVisible = true
+
+                updateRestingArrowDimens()
+                gestureEntryTime = SystemClock.uptimeMillis()
+                gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
             }
             GestureState.ACTIVE -> {
-                updateRestingArrowDimens(animated = true, currentState)
-                // Vibrate the first time we transition to ACTIVE
-                if (!hasHapticPlayed) {
-                    hasHapticPlayed = true
-                    vibrationTime = SystemClock.uptimeMillis()
-                    vibratorHelper.vibrate(VibrationEffect.EFFECT_TICK)
+                previousXTranslationOnActiveOffset = previousXTranslation
+                gestureActiveTime = SystemClock.uptimeMillis()
+
+                updateRestingArrowDimens()
+
+                vibratorHelper.cancel()
+                mainHandler.postDelayed(10L) {
+                    vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
+                }
+
+                val startingVelocity = convertVelocityToSpringStartingVelocity(
+                    valueOnFastVelocity = 0f,
+                    valueOnSlowVelocity = if (previousState == GestureState.ENTRY) 2f else 4.5f
+                )
+
+                when (previousState) {
+                    GestureState.ENTRY,
+                    GestureState.INACTIVE -> {
+                        mView.popOffEdge(startingVelocity)
+                    }
+                    GestureState.COMMITTED -> {
+                        // if previous state was committed then this activation
+                        // was due to a quick second swipe. Don't pop the arrow this time
+                    }
+                    else -> { }
                 }
             }
+
             GestureState.INACTIVE -> {
-                updateRestingArrowDimens(animated = true, currentState)
+                gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
+
+                val startingVelocity = convertVelocityToSpringStartingVelocity(
+                        valueOnFastVelocity = -1.05f,
+                        valueOnSlowVelocity = -1.50f
+                )
+                mView.popOffEdge(startingVelocity)
+
+                vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+                updateRestingArrowDimens()
             }
-            GestureState.FLUNG -> playFlingBackAnimation()
-            GestureState.COMMITTED -> playCommitBackAnimation()
-            GestureState.CANCELLED -> playCancelBackAnimation()
+            GestureState.FLUNG -> {
+                mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(1.9f) }
+                playHorizontalAnimationThen(onEndSetCommittedStateListener)
+            }
+            GestureState.COMMITTED -> {
+                if (previousState == GestureState.FLUNG) {
+                    playAnimationThenSetGoneEnd()
+                } else {
+                    mView.popScale(3f)
+                    mainHandler.postDelayed(
+                            playAnimationThenSetGoneOnAlphaEnd,
+                            MIN_DURATION_COMMITTED_ANIMATION
+                    )
+                }
+            }
+            GestureState.CANCELLED -> {
+                val delay = max(0, MIN_DURATION_CANCELLED_ANIMATION - elapsedTimeSinceEntry)
+                playWithBackgroundWidthAnimation(onEndSetGoneStateListener, delay)
+
+                params.arrowStrokeAlphaSpring.get(0f).takeIf { it.isNewState }?.let {
+                    mView.popArrowAlpha(0f, it.value)
+                }
+                mainHandler.postDelayed(10L) { vibratorHelper.cancel() }
+            }
         }
     }
 
+    private fun convertVelocityToSpringStartingVelocity(
+            valueOnFastVelocity: Float,
+            valueOnSlowVelocity: Float,
+    ): Float {
+        val factor = velocityTracker?.run {
+            computeCurrentVelocity(PX_PER_MS)
+            MathUtils.smoothStep(0f, 3f, abs(xVelocity))
+        } ?: valueOnFastVelocity
+
+        return MathUtils.lerp(valueOnFastVelocity, valueOnSlowVelocity, 1 - factor)
+    }
+
     private fun scheduleFailsafe() {
         if (!ENABLE_FAILSAFE) return
         cancelFailsafe()
@@ -685,24 +933,24 @@
     init {
         if (DEBUG) mView.drawDebugInfo = { canvas ->
             val debugStrings = listOf(
-                "$currentState",
-                "startX=$startX",
-                "startY=$startY",
-                "xDelta=${"%.1f".format(totalTouchDelta)}",
-                "xTranslation=${"%.1f".format(previousXTranslation)}",
-                "pre=${"%.0f".format(preThresholdStretchProgress(previousXTranslation) * 100)}%",
-                "post=${"%.0f".format(fullScreenStretchProgress(previousXTranslation) * 100)}%"
+                    "$currentState",
+                    "startX=$startX",
+                    "startY=$startY",
+                    "xDelta=${"%.1f".format(totalTouchDelta)}",
+                    "xTranslation=${"%.1f".format(previousXTranslation)}",
+                    "pre=${"%.0f".format(staticThresholdProgress(previousXTranslation) * 100)}%",
+                    "post=${"%.0f".format(fullScreenProgress(previousXTranslation) * 100)}%"
             )
             val debugPaint = Paint().apply {
                 color = Color.WHITE
             }
             val debugInfoBottom = debugStrings.size * 32f + 4f
             canvas.drawRect(
-                4f,
-                4f,
-                canvas.width.toFloat(),
-                debugStrings.size * 32f + 4f,
-                debugPaint
+                    4f,
+                    4f,
+                    canvas.width.toFloat(),
+                    debugStrings.size * 32f + 4f,
+                    debugPaint
             )
             debugPaint.apply {
                 color = Color.BLACK
@@ -728,9 +976,71 @@
                 canvas.drawLine(x, debugInfoBottom, x, canvas.height.toFloat(), debugPaint)
             }
 
-            drawVerticalLine(x = params.swipeTriggerThreshold, color = Color.BLUE)
+            drawVerticalLine(x = params.staticTriggerThreshold, color = Color.BLUE)
+            drawVerticalLine(x = params.deactivationSwipeTriggerThreshold, color = Color.BLUE)
             drawVerticalLine(x = startX, color = Color.GREEN)
             drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY)
         }
     }
 }
+
+/**
+ * In addition to a typical step function which returns one or two
+ * values based on a threshold, `Step` also gracefully handles quick
+ * changes in input near the threshold value that would typically
+ * result in the output rapidly changing.
+ *
+ * In the context of Back arrow, the arrow's stroke opacity should
+ * always appear transparent or opaque. Using a typical Step function,
+ * this would resulting in a flickering appearance as the output would
+ * change rapidly. `Step` addresses this by moving the threshold after
+ * it is crossed so it cannot be easily crossed again with small changes
+ * in touch events.
+ */
+class Step<T>(
+        private val threshold: Float,
+        private val factor: Float = 1.1f,
+        private val postThreshold: T,
+        private val preThreshold: T
+) {
+
+    data class Value<T>(val value: T, val isNewState: Boolean)
+
+    private val lowerFactor = 2 - factor
+
+    private lateinit var startValue: Value<T>
+    private lateinit var previousValue: Value<T>
+    private var hasCrossedUpperBoundAtLeastOnce = false
+    private var progress: Float = 0f
+
+    init {
+        reset()
+    }
+
+    fun reset() {
+        hasCrossedUpperBoundAtLeastOnce = false
+        progress = 0f
+        startValue = Value(preThreshold, false)
+        previousValue = startValue
+    }
+
+    fun get(progress: Float): Value<T> {
+        this.progress = progress
+
+        val hasCrossedUpperBound = progress > threshold * factor
+        val hasCrossedLowerBound = progress > threshold * lowerFactor
+
+        return when {
+            hasCrossedUpperBound && !hasCrossedUpperBoundAtLeastOnce -> {
+                hasCrossedUpperBoundAtLeastOnce = true
+                Value(postThreshold, true)
+            }
+            hasCrossedLowerBound -> previousValue.copy(isNewState = false)
+            hasCrossedUpperBoundAtLeastOnce -> {
+                hasCrossedUpperBoundAtLeastOnce = false
+                Value(preThreshold, true)
+            }
+            else -> startValue
+        }.also { previousValue = it }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 13c5b48..eea0e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -273,7 +273,6 @@
                 @Override
                 public void triggerBack() {
                     // Notify FalsingManager that an intentional gesture has occurred.
-                    // TODO(b/186519446): use a different method than isFalseTouch
                     mFalsingManager.isFalseTouch(BACK_GESTURE);
                     // Only inject back keycodes when ahead-of-time back dispatching is disabled.
                     if (mBackAnimation == null) {
@@ -502,6 +501,15 @@
     }
 
     private void updateIsEnabled() {
+        try {
+            Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
+            updateIsEnabledTraced();
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    private void updateIsEnabledTraced() {
         boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
         if (isEnabled == mIsEnabled) {
             return;
@@ -587,13 +595,18 @@
     }
 
     private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
-        if (mEdgeBackPlugin != null) {
-            mEdgeBackPlugin.onDestroy();
+        try {
+            Trace.beginSection("setEdgeBackPlugin");
+            if (mEdgeBackPlugin != null) {
+                mEdgeBackPlugin.onDestroy();
+            }
+            mEdgeBackPlugin = edgeBackPlugin;
+            mEdgeBackPlugin.setBackCallback(mBackCallback);
+            mEdgeBackPlugin.setLayoutParams(createLayoutParams());
+            updateDisplaySize();
+        } finally {
+            Trace.endSection();
         }
-        mEdgeBackPlugin = edgeBackPlugin;
-        mEdgeBackPlugin.setBackCallback(mBackCallback);
-        mEdgeBackPlugin.setLayoutParams(createLayoutParams());
-        updateDisplaySize();
     }
 
     public boolean isHandlingGestures() {
@@ -919,6 +932,10 @@
                             mThresholdCrossed = true;
                             // Capture inputs
                             mInputMonitor.pilferPointers();
+                            if (mBackAnimation != null) {
+                                // Notify FalsingManager that an intentional gesture has occurred.
+                                mFalsingManager.isFalseTouch(BACK_GESTURE);
+                            }
                             mInputEventReceiver.setBatchingEnabled(true);
                         } else {
                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index d56537b..0c00022 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -1,52 +1,82 @@
 package com.android.systemui.navigationbar.gestural
 
 import android.content.res.Resources
+import android.util.TypedValue
+import androidx.core.animation.PathInterpolator
+import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.R
 
 data class EdgePanelParams(private var resources: Resources) {
 
     data class ArrowDimens(
-        val length: Float = 0f,
-        val height: Float = 0f
+            val length: Float = 0f,
+            val height: Float = 0f,
+            val alpha: Float = 0f,
+            var alphaSpring: SpringForce? = null,
+            val heightSpring: SpringForce? = null,
+            val lengthSpring: SpringForce? = null,
     )
 
     data class BackgroundDimens(
-        val width: Float = 0f,
-        val height: Float = 0f,
-        val edgeCornerRadius: Float = 0f,
-        val farCornerRadius: Float = 0f
+            val width: Float? = 0f,
+            val height: Float = 0f,
+            val edgeCornerRadius: Float = 0f,
+            val farCornerRadius: Float = 0f,
+            val alpha: Float = 0f,
+            val widthSpring: SpringForce? = null,
+            val heightSpring: SpringForce? = null,
+            val farCornerRadiusSpring: SpringForce? = null,
+            val edgeCornerRadiusSpring: SpringForce? = null,
+            val alphaSpring: SpringForce? = null,
     )
 
     data class BackIndicatorDimens(
-        val horizontalTranslation: Float = 0f,
-        val arrowDimens: ArrowDimens = ArrowDimens(),
-        val backgroundDimens: BackgroundDimens = BackgroundDimens()
+            val horizontalTranslation: Float? = 0f,
+            val scale: Float = 0f,
+            val scalePivotX: Float = 0f,
+            val arrowDimens: ArrowDimens,
+            val backgroundDimens: BackgroundDimens,
+            val verticalTranslationSpring: SpringForce? = null,
+            val horizontalTranslationSpring: SpringForce? = null,
+            val scaleSpring: SpringForce? = null,
     )
 
-    var arrowThickness: Float = 0f
+    lateinit var entryIndicator: BackIndicatorDimens
         private set
-    var entryIndicator = BackIndicatorDimens()
+    lateinit var activeIndicator: BackIndicatorDimens
         private set
-    var activeIndicator = BackIndicatorDimens()
+    lateinit var cancelledIndicator: BackIndicatorDimens
         private set
-    var preThresholdIndicator = BackIndicatorDimens()
+    lateinit var flungIndicator: BackIndicatorDimens
         private set
-    var fullyStretchedIndicator = BackIndicatorDimens()
+    lateinit var committedIndicator: BackIndicatorDimens
         private set
-    var cancelledEdgeCornerRadius: Float = 0f
+    lateinit var preThresholdIndicator: BackIndicatorDimens
         private set
-    var cancelledArrowDimens = ArrowDimens()
+    lateinit var fullyStretchedIndicator: BackIndicatorDimens
+        private set
 
     // navigation bar edge constants
     var arrowPaddingEnd: Int = 0
         private set
+    var arrowThickness: Float = 0f
+        private set
+    lateinit var arrowStrokeAlphaSpring: Step<SpringForce>
+        private set
+    lateinit var arrowStrokeAlphaInterpolator: Step<Float>
+        private set
 
     // The closest to y
     var minArrowYPosition: Int = 0
         private set
     var fingerOffset: Int = 0
         private set
-    var swipeTriggerThreshold: Float = 0f
+    var staticTriggerThreshold: Float = 0f
+        private set
+    var reactivationTriggerThreshold: Float = 0f
+        private set
+    var deactivationSwipeTriggerThreshold: Float = 0f
+        get() = -field
         private set
     var swipeProgressThreshold: Float = 0f
         private set
@@ -55,6 +85,26 @@
     var minDeltaForSwitch: Int = 0
         private set
 
+    var minDragToStartAnimation: Float = 0f
+        private set
+
+    lateinit var entryWidthInterpolator: PathInterpolator
+        private set
+    lateinit var entryWidthTowardsEdgeInterpolator: PathInterpolator
+        private set
+    lateinit var activeWidthInterpolator: PathInterpolator
+        private set
+    lateinit var arrowAngleInterpolator: PathInterpolator
+        private set
+    lateinit var translationInterpolator: PathInterpolator
+        private set
+    lateinit var farCornerInterpolator: PathInterpolator
+        private set
+    lateinit var edgeCornerInterpolator: PathInterpolator
+        private set
+    lateinit var heightInterpolator: PathInterpolator
+        private set
+
     init {
         update(resources)
     }
@@ -63,6 +113,10 @@
         return resources.getDimension(id)
     }
 
+    private fun getDimenFloat(id: Int): Float {
+        return TypedValue().run { resources.getValue(id, this, true); float }
+    }
+
     private fun getPx(id: Int): Int {
         return resources.getDimensionPixelSize(id)
     }
@@ -73,72 +127,200 @@
         arrowPaddingEnd = getPx(R.dimen.navigation_edge_panel_padding)
         minArrowYPosition = getPx(R.dimen.navigation_edge_arrow_min_y)
         fingerOffset = getPx(R.dimen.navigation_edge_finger_offset)
-        swipeTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
+        staticTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
+        reactivationTriggerThreshold =
+                getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold)
+        deactivationSwipeTriggerThreshold =
+                getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold)
         swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold)
         minDeltaForSwitch = getPx(R.dimen.navigation_edge_minimum_x_delta_for_switch)
+        minDragToStartAnimation =
+                getDimen(R.dimen.navigation_edge_action_min_distance_to_start_animation)
+
+        entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
+        entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f)
+        activeWidthInterpolator = PathInterpolator(.15f, .48f, .46f, .89f)
+        arrowAngleInterpolator = entryWidthInterpolator
+        translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
+        farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f)
+        edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f)
+        heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f)
+
+        val showArrowOnProgressValue = .2f
+        val showArrowOnProgressValueFactor = 1.05f
+
+        val entryActiveHorizontalTranslationSpring = createSpring(675f, 0.8f)
+        val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f)
+        val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f)
+        val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f)
+        val flungCommittedFarCornerSpring = createSpring(10000f, 1f)
+        val flungCommittedWidthSpring = createSpring(10000f, 1f)
+        val flungCommittedHeightSpring = createSpring(10000f, 1f)
 
         entryIndicator = BackIndicatorDimens(
-            horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
-            arrowDimens = ArrowDimens(
-                length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
-                height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
-            ),
-            backgroundDimens = BackgroundDimens(
-                width = getDimen(R.dimen.navigation_edge_entry_background_width),
-                height = getDimen(R.dimen.navigation_edge_entry_background_height),
-                edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
-                farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners)
-            )
+                horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
+                scale = getDimenFloat(R.dimen.navigation_edge_entry_scale),
+                scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+                horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
+                verticalTranslationSpring = createSpring(10000f, 0.9f),
+                scaleSpring = createSpring(120f, 0.8f),
+                arrowDimens = ArrowDimens(
+                        length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
+                        height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
+                        alpha = 0f,
+                        alphaSpring = createSpring(200f, 1f),
+                        lengthSpring = createSpring(600f, 0.4f),
+                        heightSpring = createSpring(600f, 0.4f),
+                ),
+                backgroundDimens = BackgroundDimens(
+                        alpha = 1f,
+                        width = getDimen(R.dimen.navigation_edge_entry_background_width),
+                        height = getDimen(R.dimen.navigation_edge_entry_background_height),
+                        edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
+                        farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners),
+                        alphaSpring = createSpring(900f, 1f),
+                        widthSpring = createSpring(450f, 0.65f),
+                        heightSpring = createSpring(1500f, 0.45f),
+                        farCornerRadiusSpring = createSpring(300f, 0.5f),
+                        edgeCornerRadiusSpring = createSpring(150f, 0.5f),
+                )
         )
 
         activeIndicator = BackIndicatorDimens(
-            horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
-            arrowDimens = ArrowDimens(
-                length = getDimen(R.dimen.navigation_edge_active_arrow_length),
-                height = getDimen(R.dimen.navigation_edge_active_arrow_height),
-            ),
-            backgroundDimens = BackgroundDimens(
-                width = getDimen(R.dimen.navigation_edge_active_background_width),
-                height = getDimen(R.dimen.navigation_edge_active_background_height),
-                edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
-                farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners)
-
-            )
+                horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
+                scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
+                horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
+                scaleSpring = createSpring(450f, 0.415f),
+                arrowDimens = ArrowDimens(
+                        length = getDimen(R.dimen.navigation_edge_active_arrow_length),
+                        height = getDimen(R.dimen.navigation_edge_active_arrow_height),
+                        alpha = 1f,
+                        lengthSpring = activeCommittedArrowLengthSpring,
+                        heightSpring = activeCommittedArrowHeightSpring,
+                ),
+                backgroundDimens = BackgroundDimens(
+                        alpha = 1f,
+                        width = getDimen(R.dimen.navigation_edge_active_background_width),
+                        height = getDimen(R.dimen.navigation_edge_active_background_height),
+                        edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
+                        farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners),
+                        widthSpring = createSpring(375f, 0.675f),
+                        heightSpring = createSpring(10000f, 1f),
+                        edgeCornerRadiusSpring = createSpring(600f, 0.36f),
+                        farCornerRadiusSpring = createSpring(2500f, 0.855f),
+                )
         )
 
         preThresholdIndicator = BackIndicatorDimens(
-            horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin),
-            arrowDimens = ArrowDimens(
-                length = entryIndicator.arrowDimens.length,
-                height = entryIndicator.arrowDimens.height,
-            ),
-            backgroundDimens = BackgroundDimens(
-                width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
-                height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height),
-                edgeCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
-                farCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_far_corners)
-            )
+                horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin),
+                scale = getDimenFloat(R.dimen.navigation_edge_pre_threshold_scale),
+                scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+                scaleSpring = createSpring(120f, 0.8f),
+                horizontalTranslationSpring = createSpring(6000f, 1f),
+                arrowDimens = ArrowDimens(
+                        length = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_length),
+                        height = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_height),
+                        alpha = 1f,
+                        lengthSpring = createSpring(100f, 0.6f),
+                        heightSpring = createSpring(100f, 0.6f),
+                ),
+                backgroundDimens = BackgroundDimens(
+                        alpha = 1f,
+                        width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+                        height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height),
+                        edgeCornerRadius =
+                                getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
+                        farCornerRadius =
+                                getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
+                        widthSpring = createSpring(200f, 0.65f),
+                        heightSpring = createSpring(1500f, 0.45f),
+                        farCornerRadiusSpring = createSpring(200f, 1f),
+                        edgeCornerRadiusSpring = createSpring(150f, 0.5f),
+                )
+        )
+
+        committedIndicator = activeIndicator.copy(
+                horizontalTranslation = null,
+                arrowDimens = activeIndicator.arrowDimens.copy(
+                        lengthSpring = activeCommittedArrowLengthSpring,
+                        heightSpring = activeCommittedArrowHeightSpring,
+                ),
+                backgroundDimens = activeIndicator.backgroundDimens.copy(
+                        alpha = 0f,
+                        // explicitly set to null to preserve previous width upon state change
+                        width = null,
+                        widthSpring = flungCommittedWidthSpring,
+                        heightSpring = flungCommittedHeightSpring,
+                        edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
+                        farCornerRadiusSpring = flungCommittedFarCornerSpring,
+                ),
+                scale = 0.85f,
+                scaleSpring = createSpring(650f, 1f),
+        )
+
+        flungIndicator = committedIndicator.copy(
+                arrowDimens = committedIndicator.arrowDimens.copy(
+                        lengthSpring = createSpring(850f, 0.46f),
+                        heightSpring = createSpring(850f, 0.46f),
+                ),
+                backgroundDimens = committedIndicator.backgroundDimens.copy(
+                        widthSpring = flungCommittedWidthSpring,
+                        heightSpring = flungCommittedHeightSpring,
+                        edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
+                        farCornerRadiusSpring = flungCommittedFarCornerSpring,
+                )
+        )
+
+        cancelledIndicator = entryIndicator.copy(
+                backgroundDimens = entryIndicator.backgroundDimens.copy(width = 0f)
         )
 
         fullyStretchedIndicator = BackIndicatorDimens(
-            horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin),
-            arrowDimens = ArrowDimens(
-                length = getDimen(R.dimen.navigation_edge_stretched_arrow_length),
-                height = getDimen(R.dimen.navigation_edge_stretched_arrow_height),
-            ),
-            backgroundDimens = BackgroundDimens(
-                width = getDimen(R.dimen.navigation_edge_stretch_background_width),
-                height = getDimen(R.dimen.navigation_edge_stretch_background_height),
-                edgeCornerRadius = getDimen(R.dimen.navigation_edge_stretch_edge_corners),
-                farCornerRadius = getDimen(R.dimen.navigation_edge_stretch_far_corners)
+                horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin),
+                scale = getDimenFloat(R.dimen.navigation_edge_stretch_scale),
+                horizontalTranslationSpring = null,
+                verticalTranslationSpring = null,
+                scaleSpring = null,
+                arrowDimens = ArrowDimens(
+                        length = getDimen(R.dimen.navigation_edge_stretched_arrow_length),
+                        height = getDimen(R.dimen.navigation_edge_stretched_arrow_height),
+                        alpha = 1f,
+                        alphaSpring = null,
+                        heightSpring = null,
+                        lengthSpring = null,
+                ),
+                backgroundDimens = BackgroundDimens(
+                        alpha = 1f,
+                        width = getDimen(R.dimen.navigation_edge_stretch_background_width),
+                        height = getDimen(R.dimen.navigation_edge_stretch_background_height),
+                        edgeCornerRadius = getDimen(R.dimen.navigation_edge_stretch_edge_corners),
+                        farCornerRadius = getDimen(R.dimen.navigation_edge_stretch_far_corners),
+                        alphaSpring = null,
+                        widthSpring = null,
+                        heightSpring = null,
+                        edgeCornerRadiusSpring = null,
+                        farCornerRadiusSpring = null,
+                )
+        )
+
+        arrowStrokeAlphaInterpolator = Step(
+                threshold = showArrowOnProgressValue,
+                factor = showArrowOnProgressValueFactor,
+                postThreshold = 1f,
+                preThreshold = 0f
+        )
+
+        entryIndicator.arrowDimens.alphaSpring?.let { alphaSpring ->
+            arrowStrokeAlphaSpring = Step(
+                    threshold = showArrowOnProgressValue,
+                    factor = showArrowOnProgressValueFactor,
+                    postThreshold = alphaSpring,
+                    preThreshold = SpringForce().setStiffness(2000f).setDampingRatio(1f)
             )
-        )
-
-        cancelledEdgeCornerRadius = getDimen(R.dimen.navigation_edge_cancelled_edge_corners)
-
-        cancelledArrowDimens = ArrowDimens(
-            length = getDimen(R.dimen.navigation_edge_cancelled_arrow_length),
-            height = getDimen(R.dimen.navigation_edge_cancelled_arrow_height)
-        )
+        }
     }
 }
+
+fun createSpring(stiffness: Float, dampingRatio: Float): SpringForce {
+    return SpringForce().setStiffness(stiffness).setDampingRatio(dampingRatio)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 1230708..590efbb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.navigationbar.gestural;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE;
 import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
 
@@ -56,6 +54,7 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.statusbar.VibratorHelper;
 
@@ -289,7 +288,8 @@
             Context context,
             LatencyTracker latencyTracker,
             VibratorHelper vibratorHelper,
-            @Background Executor backgroundExecutor) {
+            @Background Executor backgroundExecutor,
+            DisplayTracker displayTracker) {
         super(context);
 
         mWindowManager = context.getSystemService(WindowManager.class);
@@ -365,7 +365,7 @@
 
         setVisibility(GONE);
 
-        boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
+        boolean isPrimaryDisplay = mContext.getDisplayId() == displayTracker.getDefaultDisplayId();
         mRegionSamplingHelper = new RegionSamplingHelper(this,
                 new RegionSamplingHelper.SamplingCallback() {
                     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 08d1857..6bfe1a0 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.content.ActivityNotFoundException
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
 import android.os.UserManager
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.util.kotlin.getOrNull
@@ -57,7 +59,7 @@
      * If the keyguard is locked, notes will open as a full screen experience. A locked device has
      * no contextual information which let us use the whole screen space available.
      *
-     * If no in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be
+     * If not in multi-window or the keyguard is unlocked, notes will open as a bubble OR it will be
      * collapsed if the notes bubble is already opened.
      *
      * That will let users open other apps in full screen, and take contextual notes.
@@ -68,16 +70,23 @@
         val bubbles = optionalBubbles.getOrNull() ?: return
         val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
         val userManager = optionalUserManager.getOrNull() ?: return
-        val intent = intentResolver.resolveIntent() ?: return
 
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
-        if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
-            context.startActivity(intent)
-        } else {
-            // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
-            bubbles.showOrHideAppBubble(intent)
+        val intent = intentResolver.resolveIntent() ?: return
+
+        // TODO(b/266686199): We should handle when app not available. For now, we log.
+        try {
+            if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
+                context.startActivity(intent)
+            } else {
+                bubbles.showOrHideAppBubble(intent)
+            }
+        } catch (e: ActivityNotFoundException) {
+            val message =
+                "Activity not found for action: ${NoteTaskIntentResolver.ACTION_CREATE_NOTE}."
+            Log.e(TAG, message, e)
         }
     }
 
@@ -106,6 +115,8 @@
     }
 
     companion object {
+        private val TAG = NoteTaskController::class.simpleName.orEmpty()
+
         // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
         const val NOTE_TASK_KEY_EVENT = 311
     }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
index 26e3f49..11dc1d7 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
@@ -16,67 +16,39 @@
 
 package com.android.systemui.notetask
 
-import android.content.ComponentName
+import android.app.role.RoleManager
+import android.content.Context
 import android.content.Intent
-import android.content.pm.ActivityInfo
-import android.content.pm.PackageManager
-import android.content.pm.PackageManager.ResolveInfoFlags
-import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
 import javax.inject.Inject
 
-/**
- * Class responsible to query all apps and find one that can handle the [ACTION_CREATE_NOTE]. If
- * found, an [Intent] ready for be launched will be returned. Otherwise, returns null.
- *
- * TODO(b/248274123): should be revisited once the notes role is implemented.
- */
 internal class NoteTaskIntentResolver
 @Inject
 constructor(
-    private val packageManager: PackageManager,
+    private val context: Context,
+    private val roleManager: RoleManager,
 ) {
 
     fun resolveIntent(): Intent? {
-        val intent = Intent(ACTION_CREATE_NOTE)
-        val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
-        val infoList = packageManager.queryIntentActivities(intent, flags)
+        val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, context.user).firstOrNull()
 
-        for (info in infoList) {
-            val packageName = info.activityInfo.applicationInfo.packageName ?: continue
-            val activityName = resolveActivityNameForNotesAction(packageName) ?: continue
+        if (packageName.isNullOrEmpty()) return null
 
-            return Intent(ACTION_CREATE_NOTE)
-                .setPackage(packageName)
-                .setComponent(ComponentName(packageName, activityName))
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        }
-
-        return null
-    }
-
-    private fun resolveActivityNameForNotesAction(packageName: String): String? {
-        val intent = Intent(ACTION_CREATE_NOTE).setPackage(packageName)
-        val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
-        val resolveInfo = packageManager.resolveActivity(intent, flags)
-
-        val activityInfo = resolveInfo?.activityInfo ?: return null
-        if (activityInfo.name.isNullOrBlank()) return null
-        if (!activityInfo.exported) return null
-        if (!activityInfo.enabled) return null
-        if (!activityInfo.showWhenLocked) return null
-        if (!activityInfo.turnScreenOn) return null
-
-        return activityInfo.name
+        return Intent(ACTION_CREATE_NOTE)
+            .setPackage(packageName)
+            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint was
+            // used to start it.
+            .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
     }
 
     companion object {
-        // TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead.
+        // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
         const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
+
+        // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
+        const val ROLE_NOTES = "android.app.role.NOTES"
+
+        // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
+        const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
     }
 }
-
-private val ActivityInfo.showWhenLocked: Boolean
-    get() = flags and ActivityInfo.FLAG_SHOW_WHEN_LOCKED != 0
-
-private val ActivityInfo.turnScreenOn: Boolean
-    get() = flags and ActivityInfo.FLAG_TURN_SCREEN_ON != 0
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 8bdf319..ec6a16a 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -18,11 +18,13 @@
 
 import android.app.Activity
 import android.app.KeyguardManager
+import android.app.role.RoleManager
 import android.content.Context
 import android.os.UserManager
 import androidx.core.content.getSystemService
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
 import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
 import dagger.Binds
@@ -33,20 +35,25 @@
 import java.util.Optional
 
 /** Compose all dependencies required by Note Task feature. */
-@Module
+@Module(includes = [NoteTaskQuickAffordanceModule::class])
 internal interface NoteTaskModule {
 
     @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
-    fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity?
+    fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
 
     @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
-    fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity?
+    fun CreateNoteTaskShortcutActivity.bindNoteTaskShortcutActivity(): Activity
 
     companion object {
 
         @[Provides NoteTaskEnabledKey]
-        fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
-            return featureFlags.isEnabled(Flags.NOTE_TASKS)
+        fun provideIsNoteTaskEnabled(
+            featureFlags: FeatureFlags,
+            roleManager: RoleManager,
+        ): Boolean {
+            val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.ROLE_NOTES)
+            val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
+            return isRoleAvailable && isFeatureEnabled
         }
 
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
new file mode 100644
index 0000000..cfbaa48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask.quickaffordance
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskEnabledKey
+import javax.inject.Inject
+import kotlinx.coroutines.flow.flowOf
+
+internal class NoteTaskQuickAffordanceConfig
+@Inject
+constructor(
+    context: Context,
+    private val noteTaskController: NoteTaskController,
+    @NoteTaskEnabledKey private val isEnabled: Boolean,
+) : KeyguardQuickAffordanceConfig {
+
+    override val key = BuiltInKeyguardQuickAffordanceKeys.CREATE_NOTE
+
+    override val pickerName: String = context.getString(R.string.note_task_button_label)
+
+    override val pickerIconResourceId = R.drawable.ic_note_task_shortcut_keyguard
+
+    override val lockScreenState = flowOf(getLockScreenState())
+
+    // TODO(b/265949213)
+    private fun getLockScreenState() =
+        if (isEnabled) {
+            val icon = Icon.Resource(pickerIconResourceId, ContentDescription.Loaded(pickerName))
+            LockScreenState.Visible(icon)
+        } else {
+            LockScreenState.Hidden
+        }
+
+    override suspend fun getPickerScreenState() =
+        if (isEnabled) {
+            PickerScreenState.Default()
+        } else {
+            PickerScreenState.UnavailableOnDevice
+        }
+
+    override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
+        noteTaskController.showNoteTask()
+        return OnTriggeredResult.Handled
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
new file mode 100644
index 0000000..7cb932a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask.quickaffordance
+
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+internal interface NoteTaskQuickAffordanceModule {
+
+    @[Binds IntoSet]
+    fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
index f6a623e..6ab0da6 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -46,7 +46,7 @@
                 id = SHORTCUT_ID,
                 shortLabel = getString(R.string.note_task_button_label),
                 intent = LaunchNoteTaskActivity.newIntent(context = this),
-                iconResource = R.drawable.ic_note_task_button,
+                iconResource = R.drawable.ic_note_task_shortcut_widget,
             )
         setResult(Activity.RESULT_OK, intent)
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 90fc1d7..595b882 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -68,6 +68,7 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.NotificationChannels;
@@ -175,7 +176,7 @@
     private ActivityStarter mActivityStarter;
     private final BroadcastSender mBroadcastSender;
     private final UiEventLogger mUiEventLogger;
-
+    private final UserTracker mUserTracker;
     private final Lazy<BatteryController> mBatteryControllerLazy;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
 
@@ -184,7 +185,8 @@
     @Inject
     public PowerNotificationWarnings(Context context, ActivityStarter activityStarter,
             BroadcastSender broadcastSender, Lazy<BatteryController> batteryControllerLazy,
-            DialogLaunchAnimator dialogLaunchAnimator, UiEventLogger uiEventLogger) {
+            DialogLaunchAnimator dialogLaunchAnimator, UiEventLogger uiEventLogger,
+            UserTracker userTracker) {
         mContext = context;
         mNoMan = mContext.getSystemService(NotificationManager.class);
         mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -196,6 +198,7 @@
         mDialogLaunchAnimator = dialogLaunchAnimator;
         mUseSevereDialog = mContext.getResources().getBoolean(R.bool.config_severe_battery_dialog);
         mUiEventLogger = uiEventLogger;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -692,7 +695,7 @@
                         Secure.putIntForUser(
                                 resolver,
                                 Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
-                                1, UserHandle.USER_CURRENT);
+                                1, mUserTracker.getUserId());
                     });
         } else {
             d.setTitle(R.string.battery_saver_confirmation_title);
@@ -843,7 +846,8 @@
                 logEvent(BatteryWarningEvents
                         .LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_SETTINGS);
                 dismissLowBatteryNotification();
-                mContext.startActivityAsUser(mOpenBatterySaverSettings, UserHandle.CURRENT);
+                mContext.startActivityAsUser(mOpenBatterySaverSettings,
+                        mUserTracker.getUserHandle());
             } else if (action.equals(ACTION_START_SAVER)) {
                 logEvent(BatteryWarningEvents
                         .LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON);
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 2a6ca1a..8ad2f86 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -17,11 +17,11 @@
 import android.content.Context
 import android.util.AttributeSet
 import android.view.ViewGroup
-import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
 import com.android.settingslib.Utils
 import com.android.systemui.R
+import com.android.systemui.animation.LaunchableFrameLayout
 import com.android.systemui.statusbar.events.BackgroundAnimatableView
 
 class OngoingPrivacyChip @JvmOverloads constructor(
@@ -29,7 +29,7 @@
     attrs: AttributeSet? = null,
     defStyleAttrs: Int = 0,
     defStyleRes: Int = 0
-) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
 
     private var iconMargin = 0
     private var iconSize = 0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
index b48ea23..be93550 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -43,6 +43,7 @@
 import javax.inject.Inject
 
 private const val TAG = "AutoAddTracker"
+private const val DELIMITER = ","
 
 /**
  * Class to track tiles that have been auto-added
@@ -67,7 +68,7 @@
 
     @GuardedBy("autoAdded")
     private val autoAdded = ArraySet<String>()
-    private var restoredTiles: Set<String>? = null
+    private var restoredTiles: Map<String, AutoTile>? = null
 
     override val currentUserId: Int
         get() = userId
@@ -98,25 +99,26 @@
         when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
             Settings.Secure.QS_TILES -> {
                 restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
-                        ?.split(",")
-                        ?.toSet()
+                        ?.split(DELIMITER)
+                        ?.mapIndexed(::AutoTile)
+                        ?.associateBy(AutoTile::tileType)
                         ?: run {
                             Log.w(TAG, "Null restored tiles for user $userId")
-                            emptySet()
+                            emptyMap()
                         }
             }
             Settings.Secure.QS_AUTO_ADDED_TILES -> {
-                restoredTiles?.let { tiles ->
+                restoredTiles?.let { restoredTiles ->
                     val restoredAutoAdded = intent
                             .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
-                            ?.split(",")
+                            ?.split(DELIMITER)
                             ?: emptyList()
                     val autoAddedBeforeRestore = intent
                             .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)
-                            ?.split(",")
+                            ?.split(DELIMITER)
                             ?: emptyList()
 
-                    val tilesToRemove = restoredAutoAdded.filter { it !in tiles }
+                    val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles }
                     if (tilesToRemove.isNotEmpty()) {
                         qsHost.removeTiles(tilesToRemove)
                     }
@@ -180,6 +182,9 @@
         registerBroadcastReceiver()
     }
 
+    fun getRestoredTilePosition(tile: String): Int =
+        restoredTiles?.get(tile)?.index ?: QSTileHost.POSITION_AT_END
+
     /**
      * Returns `true` if the tile has been auto-added before
      */
@@ -196,12 +201,12 @@
      */
     fun setTileAdded(tile: String) {
         val tiles = synchronized(autoAdded) {
-                if (autoAdded.add(tile)) {
-                    getTilesFromListLocked()
-                } else {
-                    null
-                }
+            if (autoAdded.add(tile)) {
+                getTilesFromListLocked()
+            } else {
+                null
             }
+        }
         tiles?.let { saveTiles(it) }
     }
 
@@ -222,7 +227,7 @@
     }
 
     private fun getTilesFromListLocked(): String {
-        return TextUtils.join(",", autoAdded)
+        return TextUtils.join(DELIMITER, autoAdded)
     }
 
     private fun saveTiles(tiles: String) {
@@ -245,7 +250,7 @@
 
     private fun getAdded(): Collection<String> {
         val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId)
-        return current?.split(",") ?: emptySet()
+        return current?.split(DELIMITER) ?: emptySet()
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -281,4 +286,6 @@
             )
         }
     }
+
+    private data class AutoTile(val index: Int, val tileType: String)
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 100853c..98af9df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -314,7 +314,6 @@
         if (!TILES_SETTING.equals(key)) {
             return;
         }
-        Log.d(TAG, "Recreating tiles");
         if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
@@ -327,6 +326,7 @@
             }
         }
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
+        Log.d(TAG, "Recreating tiles: " + tileSpecs);
         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
                 tile -> {
                     Log.d(TAG, "Destroying tile: " + tile.getKey());
@@ -372,6 +372,8 @@
                             Log.d(TAG, "Destroying not available tile: " + tileSpec);
                             mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
                         }
+                    } else {
+                        Log.d(TAG, "No factory for a spec: " + tileSpec);
                     }
                 } catch (Throwable t) {
                     Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index 39d081d..36dc743 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -83,8 +83,7 @@
                     if (mListeners.size() > 0) {
                         mSecureSettings.unregisterContentObserver(mContentObserver);
                         mSecureSettings.registerContentObserverForUser(
-                                Settings.Secure.getUriFor(
-                                        Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED),
+                                Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
                                 false, mContentObserver, newUser);
                     }
                 }
@@ -100,8 +99,7 @@
                 mListeners.add(listener);
                 if (mListeners.size() == 1) {
                     mSecureSettings.registerContentObserverForUser(
-                            Settings.Secure.getUriFor(
-                                    Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED),
+                            Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
                             false, mContentObserver, mUserTracker.getUserId());
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index cfda9fd..7c2536d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.qs.external;
 
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
 
 import android.app.PendingIntent;
@@ -63,6 +62,7 @@
 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.DisplayTracker;
 
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -90,6 +90,7 @@
     private final TileServiceManager mServiceManager;
     private final int mUser;
     private final CustomTileStatePersister mCustomTileStatePersister;
+    private final DisplayTracker mDisplayTracker;
     @Nullable
     private android.graphics.drawable.Icon mDefaultIcon;
     @Nullable
@@ -120,7 +121,8 @@
             String action,
             Context userContext,
             CustomTileStatePersister customTileStatePersister,
-            TileServices tileServices
+            TileServices tileServices,
+            DisplayTracker displayTracker
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -135,6 +137,7 @@
         mServiceManager = tileServices.getTileWrapper(this);
         mService = mServiceManager.getTileService();
         mCustomTileStatePersister = customTileStatePersister;
+        mDisplayTracker = displayTracker;
     }
 
     @Override
@@ -310,7 +313,7 @@
         mIsShowingDialog = false;
         try {
             if (DEBUG) Log.d(TAG, "Removing token");
-            mWindowManager.removeWindowToken(mToken, DEFAULT_DISPLAY);
+            mWindowManager.removeWindowToken(mToken, mDisplayTracker.getDefaultDisplayId());
         } catch (RemoteException e) {
         }
     }
@@ -335,7 +338,8 @@
                 if (mIsTokenGranted && !mIsShowingDialog) {
                     try {
                         if (DEBUG) Log.d(TAG, "Removing token");
-                        mWindowManager.removeWindowToken(mToken, DEFAULT_DISPLAY);
+                        mWindowManager.removeWindowToken(mToken,
+                                mDisplayTracker.getDefaultDisplayId());
                     } catch (RemoteException e) {
                     }
                     mIsTokenGranted = false;
@@ -354,7 +358,7 @@
         if (mIsTokenGranted) {
             try {
                 if (DEBUG) Log.d(TAG, "Removing token");
-                mWindowManager.removeWindowToken(mToken, DEFAULT_DISPLAY);
+                mWindowManager.removeWindowToken(mToken, mDisplayTracker.getDefaultDisplayId());
             } catch (RemoteException e) {
             }
         }
@@ -398,8 +402,8 @@
         mViewClicked = view;
         try {
             if (DEBUG) Log.d(TAG, "Adding token");
-            mWindowManager.addWindowToken(mToken, TYPE_QS_DIALOG, DEFAULT_DISPLAY,
-                    null /* options */);
+            mWindowManager.addWindowToken(mToken, TYPE_QS_DIALOG,
+                    mDisplayTracker.getDefaultDisplayId(), null /* options */);
             mIsTokenGranted = true;
         } catch (RemoteException e) {
         }
@@ -566,6 +570,7 @@
         final QSLogger mQSLogger;
         final CustomTileStatePersister mCustomTileStatePersister;
         private TileServices mTileServices;
+        final DisplayTracker mDisplayTracker;
 
         Context mUserContext;
         String mSpec = "";
@@ -581,7 +586,8 @@
                 ActivityStarter activityStarter,
                 QSLogger qsLogger,
                 CustomTileStatePersister customTileStatePersister,
-                TileServices tileServices
+                TileServices tileServices,
+                DisplayTracker displayTracker
         ) {
             mQSHostLazy = hostLazy;
             mBackgroundLooper = backgroundLooper;
@@ -593,6 +599,7 @@
             mQSLogger = qsLogger;
             mCustomTileStatePersister = customTileStatePersister;
             mTileServices = tileServices;
+            mDisplayTracker = displayTracker;
         }
 
         Builder setSpec(@NonNull String spec) {
@@ -623,7 +630,8 @@
                     action,
                     mUserContext,
                     mCustomTileStatePersister,
-                    mTileServices
+                    mTileServices,
+                    mDisplayTracker
             );
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 51de522..1c60486 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -255,17 +255,19 @@
                 Log.d(TAG, "setWifiIndicators: " + indicators);
             }
             mWifiInfo.mEnabled = indicators.enabled;
-            if (indicators.qsIcon == null) {
-                return;
-            }
-            mWifiInfo.mConnected = indicators.qsIcon.visible;
-            mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
-            mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
-            mWifiInfo.mEnabled = indicators.enabled;
             mWifiInfo.mSsid = indicators.description;
             mWifiInfo.mIsTransient = indicators.isTransient;
             mWifiInfo.mStatusLabel = indicators.statusLabel;
-            refreshState(mWifiInfo);
+            if (indicators.qsIcon != null) {
+                mWifiInfo.mConnected = indicators.qsIcon.visible;
+                mWifiInfo.mWifiSignalIconId = indicators.qsIcon.icon;
+                mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
+                refreshState(mWifiInfo);
+            } else {
+                mWifiInfo.mConnected = false;
+                mWifiInfo.mWifiSignalIconId = 0;
+                mWifiInfo.mWifiSignalContentDescription = null;
+            }
         }
 
         @Override
@@ -529,6 +531,9 @@
         if (DEBUG) {
             Log.d(TAG, "handleUpdateEthernetState: " + "EthernetCallbackInfo = " + cb.toString());
         }
+        if (!cb.mConnected) {
+            return;
+        }
         final Resources r = mContext.getResources();
         state.label = r.getString(R.string.quick_settings_internet_label);
         state.state = Tile.STATE_ACTIVE;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 64a8a14..ad00069 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -171,8 +171,9 @@
             getHost().collapsePanels();
         };
 
-        Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
+        final Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
                 mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
+
         ActivityStarter.OnDismissAction dismissAction = () -> {
             if (shouldAnimateFromView) {
                 mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4d005be..a979e5a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -17,11 +17,9 @@
 package com.android.systemui.recents;
 
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
@@ -31,6 +29,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
 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_TRACING_ENABLED;
@@ -44,8 +43,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.graphics.Insets;
-import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.Binder;
@@ -77,6 +74,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -90,11 +88,11 @@
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.buttons.KeyButtonView;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -147,6 +145,7 @@
     private final UserTracker mUserTracker;
     private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
     private final UiEventLogger mUiEventLogger;
+    private final DisplayTracker mDisplayTracker;
 
     private Region mActiveNavBarRegion;
     private SurfaceControl mNavigationBarSurface;
@@ -228,11 +227,11 @@
 
         @Override
         public void onImeSwitcherPressed() {
-            // TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since
+            // TODO(b/204901476) We're intentionally using the default display for now since
             // Launcher/Taskbar isn't display aware.
             mContext.getSystemService(InputMethodManager.class)
                     .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
-                            DEFAULT_DISPLAY);
+                            mDisplayTracker.getDefaultDisplayId());
             mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
         }
 
@@ -311,7 +310,7 @@
                         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
                         intent.addFlags(
                                 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+                        mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
                     });
         }
 
@@ -322,18 +321,8 @@
         }
 
         @Override
-        public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
-                Insets visibleInsets, Task.TaskKey task) {
-            mScreenshotHelper.provideScreenshot(
-                    screenImageBundle,
-                    locationInScreen,
-                    visibleInsets,
-                    task.id,
-                    task.userId,
-                    task.sourceComponent,
-                    SCREENSHOT_OVERVIEW,
-                    mHandler,
-                    null);
+        public void takeScreenshot(ScreenshotRequest request) {
+            mScreenshotHelper.takeScreenshot(request, mHandler, null);
         }
 
         @Override
@@ -520,6 +509,7 @@
             UserTracker userTracker,
             ScreenLifecycle screenLifecycle,
             UiEventLogger uiEventLogger,
+            DisplayTracker displayTracker,
             KeyguardUnlockAnimationController sysuiUnlockAnimationController,
             AssistUtils assistUtils,
             DumpManager dumpManager) {
@@ -547,6 +537,7 @@
         mSysUiState = sysUiState;
         mSysUiState.addCallback(this::notifySystemUiStateFlags);
         mUiEventLogger = uiEventLogger;
+        mDisplayTracker = displayTracker;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
@@ -665,13 +656,14 @@
     }
 
     private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
-            boolean bouncerShowing, boolean isDozing, boolean panelExpanded) {
+            boolean bouncerShowing, boolean isDozing, boolean panelExpanded, boolean isDreaming) {
         mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
                         keyguardShowing && !keyguardOccluded)
                 .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
                         keyguardShowing && keyguardOccluded)
                 .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
                 .setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
+                .setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming)
                 .commitUpdate(mContext.getDisplayId());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index b8684ee..db2e62b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -36,6 +36,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
@@ -46,6 +48,8 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  * Helper class to initiate a screen recording
  */
@@ -60,6 +64,8 @@
     private CountDownTimer mCountDownTimer = null;
     private final Executor mMainExecutor;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final Context mContext;
+    private final FeatureFlags mFlags;
     private final UserContextProvider mUserContextProvider;
     private final UserTracker mUserTracker;
 
@@ -70,6 +76,8 @@
     private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
             new CopyOnWriteArrayList<>();
 
+    private final Lazy<ScreenCaptureDevicePolicyResolver> mDevicePolicyResolver;
+
     @VisibleForTesting
     final UserTracker.Callback mUserChangedCallback =
             new UserTracker.Callback() {
@@ -100,22 +108,44 @@
     @Inject
     public RecordingController(@Main Executor mainExecutor,
             BroadcastDispatcher broadcastDispatcher,
+            Context context,
+            FeatureFlags flags,
             UserContextProvider userContextProvider,
+            Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
             UserTracker userTracker) {
         mMainExecutor = mainExecutor;
+        mContext = context;
+        mFlags = flags;
+        mDevicePolicyResolver = devicePolicyResolver;
         mBroadcastDispatcher = broadcastDispatcher;
         mUserContextProvider = userContextProvider;
         mUserTracker = userTracker;
     }
 
-    /** Create a dialog to show screen recording options to the user. */
+    /**
+     * MediaProjection host is SystemUI for the screen recorder, so return 'my user handle'
+     */
+    private UserHandle getHostUserHandle() {
+        return UserHandle.of(UserHandle.myUserId());
+    }
+
+    /** Create a dialog to show screen recording options to the user.
+     *  If screen capturing is currently not allowed it will return a dialog
+     *  that warns users about it. */
     public Dialog createScreenRecordDialog(Context context, FeatureFlags flags,
                                            DialogLaunchAnimator dialogLaunchAnimator,
                                            ActivityStarter activityStarter,
                                            @Nullable Runnable onStartRecordingClicked) {
+        if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)
+                && mDevicePolicyResolver.get()
+                        .isScreenCaptureCompletelyDisabled(getHostUserHandle())) {
+            return new ScreenCaptureDisabledDialog(mContext);
+        }
+
         return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
-                ? new ScreenRecordPermissionDialog(context, this, activityStarter,
-                        dialogLaunchAnimator, mUserContextProvider, onStartRecordingClicked)
+                ? new ScreenRecordPermissionDialog(context,  getHostUserHandle(), this,
+                    activityStarter, dialogLaunchAnimator, mUserContextProvider,
+                    onStartRecordingClicked)
                 : new ScreenRecordDialog(context, this, activityStarter,
                 mUserContextProvider, flags, dialogLaunchAnimator, onStartRecordingClicked);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 68e3dcd..dd21be9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -42,6 +42,7 @@
 /** Dialog to select screen recording options */
 class ScreenRecordPermissionDialog(
     context: Context?,
+    private val hostUserHandle: UserHandle,
     private val controller: RecordingController,
     private val activityStarter: ActivityStarter,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
@@ -79,11 +80,9 @@
                     CaptureTargetResultReceiver()
                 )
 
-                // Send SystemUI's user handle as the host app user handle because SystemUI
-                // is the 'host app' (the app that receives screen capture data)
                 intent.putExtra(
                     MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
-                    UserHandle.of(UserHandle.myUserId())
+                    hostUserHandle
                 )
 
                 val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 017e57f..310baaf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -25,8 +25,22 @@
 import com.android.systemui.R
 
 object ActionIntentCreator {
+    /** @return a chooser intent to share the given URI. */
+    fun createShareIntent(uri: Uri) = createShareIntent(uri, null, null)
+
     /** @return a chooser intent to share the given URI with the optional provided subject. */
-    fun createShareIntent(uri: Uri, subject: String?): Intent {
+    fun createShareIntentWithSubject(uri: Uri, subject: String?) =
+        createShareIntent(uri, subject = subject)
+
+    /** @return a chooser intent to share the given URI with the optional provided extra text. */
+    fun createShareIntentWithExtraText(uri: Uri, extraText: String?) =
+        createShareIntent(uri, extraText = extraText)
+
+    private fun createShareIntent(
+        uri: Uri,
+        subject: String? = null,
+        extraText: String? = null
+    ): Intent {
         // Create a share intent, this will always go through the chooser activity first
         // which should not trigger auto-enter PiP
         val sharingIntent =
@@ -43,6 +57,7 @@
                     )
 
                 putExtra(Intent.EXTRA_SUBJECT, subject)
+                putExtra(Intent.EXTRA_TEXT, extraText)
                 addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
                 addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 01e32b7a..aa8e2c0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -22,7 +22,6 @@
 import android.os.RemoteException
 import android.os.UserHandle
 import android.util.Log
-import android.view.Display
 import android.view.IRemoteAnimationFinishedCallback
 import android.view.IRemoteAnimationRunner
 import android.view.RemoteAnimationAdapter
@@ -33,6 +32,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.DisplayTracker
 import javax.inject.Inject
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineDispatcher
@@ -47,6 +47,7 @@
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val context: Context,
+    private val displayTracker: DisplayTracker
 ) {
     /**
      * Execute the given intent with startActivity while performing operations for screenshot action
@@ -82,7 +83,7 @@
             val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
             try {
                 WindowManagerGlobal.getWindowManagerService()
-                    .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
+                    .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
             } catch (e: Exception) {
                 Log.e(TAG, "Error overriding screenshot app transition", e)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index 814b8e9..4f5cb72 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.screenshot;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_EDIT;
 import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE;
 import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
@@ -35,6 +33,7 @@
 import android.view.RemoteAnimationAdapter;
 import android.view.WindowManagerGlobal;
 
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -52,14 +51,17 @@
     private final CentralSurfaces mCentralSurfaces;
     private final ActivityManagerWrapper mActivityManagerWrapper;
     private final ScreenshotSmartActions mScreenshotSmartActions;
+    private final DisplayTracker mDisplayTracker;
 
     @Inject
     public ActionProxyReceiver(Optional<CentralSurfaces> centralSurfacesOptional,
             ActivityManagerWrapper activityManagerWrapper,
-            ScreenshotSmartActions screenshotSmartActions) {
+            ScreenshotSmartActions screenshotSmartActions,
+            DisplayTracker displayTracker) {
         mCentralSurfaces = centralSurfacesOptional.orElse(null);
         mActivityManagerWrapper = activityManagerWrapper;
         mScreenshotSmartActions = screenshotSmartActions;
+        mDisplayTracker = displayTracker;
     }
 
     @Override
@@ -78,7 +80,8 @@
                             ScreenshotController.SCREENSHOT_REMOTE_RUNNER, 0, 0);
                     try {
                         WindowManagerGlobal.getWindowManagerService()
-                                .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
+                                .overridePendingAppTransitionRemote(runner,
+                                        mDisplayTracker.getDefaultDisplayId());
                     } catch (Exception e) {
                         Log.e(TAG, "Error overriding screenshot app transition", e);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java
new file mode 100644
index 0000000..146e576
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/AssistContentRequester.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 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.systemui.screenshot;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.IAssistDataReceiver;
+import android.app.assist.AssistContent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Can be used to request the AssistContent from a provided task id, useful for getting the web uri
+ * if provided from the task.
+ *
+ * Forked from
+ * packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
+ */
+@SysUISingleton
+public class AssistContentRequester {
+    private static final String TAG = "AssistContentRequester";
+    private static final String ASSIST_KEY_CONTENT = "content";
+
+    /** For receiving content, called on the main thread. */
+    public interface Callback {
+        /**
+         * Called when the {@link android.app.assist.AssistContent} of the requested task is
+         * available.
+         **/
+        void onAssistContentAvailable(AssistContent assistContent);
+    }
+
+    private final IActivityTaskManager mActivityTaskManager;
+    private final String mPackageName;
+    private final Executor mCallbackExecutor;
+    private final Executor mSystemInteractionExecutor;
+
+    // If system loses the callback, our internal cache of original callback will also get cleared.
+    private final Map<Object, Callback> mPendingCallbacks =
+            Collections.synchronizedMap(new WeakHashMap<>());
+
+    @Inject
+    public AssistContentRequester(Context context, @Main Executor mainExecutor,
+            @Background Executor bgExecutor) {
+        mActivityTaskManager = ActivityTaskManager.getService();
+        mPackageName = context.getApplicationContext().getPackageName();
+        mCallbackExecutor = mainExecutor;
+        mSystemInteractionExecutor = bgExecutor;
+    }
+
+    /**
+     * Request the {@link AssistContent} from the task with the provided id.
+     *
+     * @param taskId to query for the content.
+     * @param callback to call when the content is available, called on the main thread.
+     */
+    public void requestAssistContent(final int taskId, final Callback callback) {
+        // ActivityTaskManager interaction here is synchronous, so call off the main thread.
+        mSystemInteractionExecutor.execute(() -> {
+            try {
+                mActivityTaskManager.requestAssistDataForTask(
+                        new AssistDataReceiver(callback, this), taskId, mPackageName);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
+            }
+        });
+    }
+
+    private void executeOnMainExecutor(Runnable callback) {
+        mCallbackExecutor.execute(callback);
+    }
+
+    private static final class AssistDataReceiver extends IAssistDataReceiver.Stub {
+
+        // The AssistDataReceiver binder callback object is passed to a system server, that may
+        // keep hold of it for longer than the lifetime of the AssistContentRequester object,
+        // potentially causing a memory leak. In the callback passed to the system server, only
+        // keep a weak reference to the parent object and lookup its callback if it still exists.
+        private final WeakReference<AssistContentRequester> mParentRef;
+        private final Object mCallbackKey = new Object();
+
+        AssistDataReceiver(Callback callback, AssistContentRequester parent) {
+            parent.mPendingCallbacks.put(mCallbackKey, callback);
+            mParentRef = new WeakReference<>(parent);
+        }
+
+        @Override
+        public void onHandleAssistData(Bundle data) {
+            if (data == null) {
+                return;
+            }
+
+            final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
+            if (content == null) {
+                Log.e(TAG, "Received AssistData, but no AssistContent found");
+                return;
+            }
+
+            AssistContentRequester requester = mParentRef.get();
+            if (requester != null) {
+                Callback callback = requester.mPendingCallbacks.get(mCallbackKey);
+                if (callback != null) {
+                    requester.executeOnMainExecutor(
+                            () -> callback.onAssistContentAvailable(content));
+                } else {
+                    Log.d(TAG, "Callback received after calling UI was disposed of");
+                }
+            } else {
+                Log.d(TAG, "Callback received after Requester was collected");
+            }
+        }
+
+        @Override
+        public void onHandleAssistScreenshot(Bitmap screenshot) {}
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 5450db9..ca8e101 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -49,6 +49,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
+import com.android.systemui.settings.UserTracker;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -79,6 +80,7 @@
     private final LongScreenshotData mLongScreenshotHolder;
     private final ActionIntentExecutor mActionExecutor;
     private final FeatureFlags mFeatureFlags;
+    private final UserTracker mUserTracker;
 
     private ImageView mPreview;
     private ImageView mTransitionView;
@@ -110,7 +112,7 @@
     public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
             @Main Executor mainExecutor, @Background Executor bgExecutor,
             LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags, UserTracker userTracker) {
         mUiEventLogger = uiEventLogger;
         mUiExecutor = mainExecutor;
         mBackgroundExecutor = bgExecutor;
@@ -118,6 +120,7 @@
         mLongScreenshotHolder = longScreenshotHolder;
         mActionExecutor = actionExecutor;
         mFeatureFlags = featureFlags;
+        mUserTracker = userTracker;
     }
 
 
@@ -363,7 +366,7 @@
 
     private void doShare(Uri uri) {
         if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
-            Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null);
+            Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri);
             mActionExecutor.launchIntentAsync(shareIntent, null,
                     mScreenshotUserHandle.getIdentifier(), false);
         } else {
@@ -375,7 +378,7 @@
             Intent sharingChooserIntent = Intent.createChooser(intent, null)
                     .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
-            startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+            startActivityAsUser(sharingChooserIntent, mUserTracker.getUserHandle());
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
new file mode 100644
index 0000000..ad66514
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MessageContainerController.kt
@@ -0,0 +1,145 @@
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.os.UserHandle
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import android.view.ViewTreeObserver
+import android.view.animation.AccelerateDecelerateInterpolator
+import androidx.constraintlayout.widget.Guideline
+import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+/**
+ * MessageContainerController controls the display of content in the screenshot message container.
+ */
+class MessageContainerController
+@Inject
+constructor(
+    private val workProfileMessageController: WorkProfileMessageController,
+    private val screenshotDetectionController: ScreenshotDetectionController,
+    private val featureFlags: FeatureFlags,
+) {
+    private lateinit var container: ViewGroup
+    private lateinit var guideline: Guideline
+    private lateinit var workProfileFirstRunView: ViewGroup
+    private lateinit var detectionNoticeView: ViewGroup
+    private var animateOut: Animator? = null
+
+    fun setView(screenshotView: ViewGroup) {
+        container = screenshotView.requireViewById(R.id.screenshot_message_container)
+        guideline = screenshotView.requireViewById(R.id.guideline)
+
+        workProfileFirstRunView = container.requireViewById(R.id.work_profile_first_run)
+        detectionNoticeView = container.requireViewById(R.id.screenshot_detection_notice)
+
+        // Restore to starting state.
+        container.visibility = View.GONE
+        guideline.setGuidelineEnd(0)
+        workProfileFirstRunView.visibility = View.GONE
+        detectionNoticeView.visibility = View.GONE
+    }
+
+    // Minimal implementation for use when Flags.SCREENSHOT_METADATA isn't turned on.
+    fun onScreenshotTaken(userHandle: UserHandle) {
+        if (featureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+            val workProfileData = workProfileMessageController.onScreenshotTaken(userHandle)
+            if (workProfileData != null) {
+                workProfileFirstRunView.visibility = View.VISIBLE
+                detectionNoticeView.visibility = View.GONE
+
+                workProfileMessageController.populateView(
+                    workProfileFirstRunView,
+                    workProfileData,
+                    this::animateOutMessageContainer
+                )
+                animateInMessageContainer()
+            }
+        }
+    }
+
+    fun onScreenshotTaken(screenshot: ScreenshotData) {
+        if (featureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+            val workProfileData =
+                workProfileMessageController.onScreenshotTaken(screenshot.userHandle)
+            var notifiedApps: List<CharSequence> = listOf()
+            if (featureFlags.isEnabled(Flags.SCREENSHOT_DETECTION)) {
+                notifiedApps = screenshotDetectionController.maybeNotifyOfScreenshot(screenshot)
+            }
+
+            // If work profile first run needs to show, bias towards that, otherwise show screenshot
+            // detection notification if needed.
+            if (workProfileData != null) {
+                workProfileFirstRunView.visibility = View.VISIBLE
+                detectionNoticeView.visibility = View.GONE
+                workProfileMessageController.populateView(
+                    workProfileFirstRunView,
+                    workProfileData,
+                    this::animateOutMessageContainer
+                )
+                animateInMessageContainer()
+            } else if (notifiedApps.isNotEmpty()) {
+                detectionNoticeView.visibility = View.VISIBLE
+                workProfileFirstRunView.visibility = View.GONE
+                screenshotDetectionController.populateView(detectionNoticeView, notifiedApps)
+                animateInMessageContainer()
+            }
+        }
+    }
+
+    private fun animateInMessageContainer() {
+        if (container.visibility == View.VISIBLE) return
+
+        // Need the container to be fully measured before animating in (to know animation offset
+        // destination)
+        container.visibility = View.VISIBLE
+        container.viewTreeObserver.addOnPreDrawListener(
+            object : ViewTreeObserver.OnPreDrawListener {
+                override fun onPreDraw(): Boolean {
+                    container.viewTreeObserver.removeOnPreDrawListener(this)
+                    getAnimator(true).start()
+                    return false
+                }
+            }
+        )
+    }
+
+    private fun animateOutMessageContainer() {
+        if (animateOut != null) return
+
+        animateOut =
+            getAnimator(false).apply {
+                addListener(
+                    object : AnimatorListenerAdapter() {
+                        override fun onAnimationEnd(animation: Animator) {
+                            super.onAnimationEnd(animation)
+                            container.visibility = View.GONE
+                            animateOut = null
+                        }
+                    }
+                )
+                start()
+            }
+    }
+
+    private fun getAnimator(animateIn: Boolean): Animator {
+        val params = container.layoutParams as MarginLayoutParams
+        val offset = container.height + params.topMargin + params.bottomMargin
+        val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f)
+        with(anim) {
+            duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS
+            interpolator = AccelerateDecelerateInterpolator()
+            addUpdateListener { valueAnimator: ValueAnimator ->
+                val interpolation = valueAnimator.animatedValue as Float
+                guideline.setGuidelineEnd((interpolation * offset).toInt())
+                container.alpha = interpolation
+            }
+        }
+        return anim
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 95cc0dc..4db48ac 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -19,33 +19,33 @@
 import android.graphics.Insets
 import android.util.Log
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
-import java.util.function.Consumer
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
+import java.util.function.Consumer
+import javax.inject.Inject
 
 /**
  * Processes a screenshot request sent from {@link ScreenshotHelper}.
  */
 @SysUISingleton
 class RequestProcessor @Inject constructor(
-    private val capture: ImageCapture,
-    private val policy: ScreenshotPolicy,
-    private val flags: FeatureFlags,
-    /** For the Java Async version, to invoke the callback. */
-    @Application private val mainScope: CoroutineScope
+        private val capture: ImageCapture,
+        private val policy: ScreenshotPolicy,
+        private val flags: FeatureFlags,
+        /** For the Java Async version, to invoke the callback. */
+        @Application private val mainScope: CoroutineScope
 ) {
     /**
      * Inspects the incoming request, returning a potentially modified request depending on policy.
      *
      * @param request the request to process
      */
+    // TODO: Delete once SCREENSHOT_METADATA flag is launched
     suspend fun process(request: ScreenshotRequest): ScreenshotRequest {
         var result = request
 
@@ -58,7 +58,7 @@
         // regardless of the managed profile status.
 
         if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
-            flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+                flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
         ) {
 
             val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
@@ -66,17 +66,21 @@
 
             result = if (policy.isManagedProfile(info.user.identifier)) {
                 val image = capture.captureTask(info.taskId)
-                    ?: error("Task snapshot returned a null Bitmap!")
+                        ?: error("Task snapshot returned a null Bitmap!")
 
                 // Provide the task snapshot as the screenshot
-                ScreenshotRequest(
-                    TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
-                    HardwareBitmapBundler.hardwareBitmapToBundle(image),
-                    info.bounds, Insets.NONE, info.taskId, info.user.identifier, info.component
-                )
+                ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source)
+                        .setTopComponent(info.component)
+                        .setTaskId(info.taskId)
+                        .setUserId(info.user.identifier)
+                        .setBitmap(image)
+                        .setBoundsOnScreen(info.bounds)
+                        .setInsets(Insets.NONE)
+                        .build()
             } else {
                 // Create a new request of the same type which includes the top component
-                ScreenshotRequest(request.type, request.source, info.component)
+                ScreenshotRequest.Builder(request.type, request.source)
+                        .setTopComponent(info.component).build()
             }
         }
 
@@ -90,12 +94,67 @@
      * @param request the request to process
      * @param callback the callback to provide the processed request, invoked from the main thread
      */
+    // TODO: Delete once SCREENSHOT_METADATA flag is launched
     fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) {
         mainScope.launch {
             val result = process(request)
             callback.accept(result)
         }
     }
+
+    /**
+     * Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
+     *
+     * @param screenshot the screenshot to process
+     */
+    suspend fun process(screenshot: ScreenshotData): ScreenshotData {
+        var result = screenshot
+
+        // Apply work profile screenshots policy:
+        //
+        // If the focused app belongs to a work profile, transforms a full screen
+        // (or partial) screenshot request to a task snapshot (provided image) screenshot.
+
+        // Whenever displayContentInfo is fetched, the topComponent is also populated
+        // regardless of the managed profile status.
+
+        if (screenshot.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
+            flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+        ) {
+            val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
+            Log.d(TAG, "findPrimaryContent: $info")
+            result.taskId = info.taskId
+            result.topComponent = info.component
+            result.userHandle = info.user
+
+            if (policy.isManagedProfile(info.user.identifier)) {
+                val image = capture.captureTask(info.taskId)
+                    ?: error("Task snapshot returned a null Bitmap!")
+
+                // Provide the task snapshot as the screenshot
+                result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
+                result.bitmap = image
+                result.screenBounds = info.bounds
+            }
+        }
+
+        return result
+    }
+
+    /**
+     * Note: This is for compatibility with existing Java. Prefer the suspending function when
+     * calling from a Coroutine context.
+     *
+     * @param screenshot the screenshot to process
+     * @param callback the callback to provide the processed screenshot, invoked from the main
+     *                 thread
+     */
+    fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
+        mainScope.launch {
+            val result = process(screenshot)
+            callback.accept(result)
+        }
+    }
 }
 
 private const val TAG = "RequestProcessor"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b21a485..72a8e23 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -17,7 +17,6 @@
 package com.android.systemui.screenshot;
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
 
 import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
@@ -44,6 +43,7 @@
 import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
 import android.app.ICompatCameraControlCallback;
 import android.app.Notification;
+import android.app.assist.AssistContent;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -101,6 +101,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.util.Assert;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -272,6 +273,7 @@
     private final ScrollCaptureClient mScrollCaptureClient;
     private final PhoneWindow mWindow;
     private final DisplayManager mDisplayManager;
+    private final DisplayTracker mDisplayTracker;
     private final ScrollCaptureController mScrollCaptureController;
     private final LongScreenshotData mLongScreenshotHolder;
     private final boolean mIsLowRamDevice;
@@ -280,7 +282,7 @@
     private final TimeoutHandler mScreenshotHandler;
     private final ActionIntentExecutor mActionExecutor;
     private final UserManager mUserManager;
-    private final WorkProfileMessageController mWorkProfileMessageController;
+    private final AssistContentRequester mAssistContentRequester;
 
     private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
         if (DEBUG_INPUT) {
@@ -290,6 +292,7 @@
     };
 
     private ScreenshotView mScreenshotView;
+    private final MessageContainerController mMessageContainerController;
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
     private boolean mScreenshotTakenInPortrait;
@@ -328,7 +331,9 @@
             ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
             ActionIntentExecutor actionExecutor,
             UserManager userManager,
-            WorkProfileMessageController workProfileMessageController
+            AssistContentRequester assistContentRequester,
+            MessageContainerController messageContainerController,
+            DisplayTracker displayTracker
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
@@ -354,13 +359,15 @@
         });
 
         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+        mDisplayTracker = displayTracker;
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
         mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mFlags = flags;
         mActionExecutor = actionExecutor;
         mUserManager = userManager;
-        mWorkProfileMessageController = workProfileMessageController;
+        mMessageContainerController = messageContainerController;
+        mAssistContentRequester = assistContentRequester;
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
@@ -390,16 +397,147 @@
                 ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
     }
 
+    void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
+            RequestCallback requestCallback) {
+        Assert.isMainThread();
+        mCurrentRequestCallback = requestCallback;
+        if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
+            Rect bounds = getFullScreenRect();
+            screenshot.setBitmap(
+                    mImageCapture.captureDisplay(mDisplayTracker.getDefaultDisplayId(), bounds));
+            screenshot.setScreenBounds(bounds);
+        }
+
+        if (screenshot.getBitmap() == null) {
+            Log.e(TAG, "handleScreenshot: Screenshot bitmap was null");
+            mNotificationsController.notifyScreenshotError(
+                    R.string.screenshot_failed_to_capture_text);
+            if (mCurrentRequestCallback != null) {
+                mCurrentRequestCallback.reportError();
+            }
+            return;
+        }
+
+        if (!isUserSetupComplete(Process.myUserHandle())) {
+            Log.w(TAG, "User setup not complete, displaying toast only");
+            // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
+            // and sharing shouldn't be exposed to the user.
+            saveScreenshotAndToast(screenshot.getUserHandle(), finisher);
+            return;
+        }
+
+        mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
+                ClipboardOverlayController.SELF_PERMISSION);
+
+        mScreenshotTakenInPortrait =
+                mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
+
+        String oldPackageName = mPackageName;
+        mPackageName = screenshot.getPackageNameString();
+
+        mScreenBitmap = screenshot.getBitmap();
+        // Optimizations
+        mScreenBitmap.setHasAlpha(false);
+        mScreenBitmap.prepareToDraw();
+
+        prepareViewForNewScreenshot(screenshot, oldPackageName);
+
+        saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
+                this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+
+        // The window is focusable by default
+        setWindowFocusable(true);
+        mScreenshotView.requestFocus();
+
+        enqueueScrollCaptureRequest(screenshot.getUserHandle());
+
+        attachWindow();
+
+        boolean showFlash = true;
+        if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+            if (screenshot.getScreenBounds() != null
+                    && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
+                            screenshot.getScreenBounds())) {
+                showFlash = false;
+            } else {
+                showFlash = true;
+                screenshot.setInsets(Insets.NONE);
+                screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
+                        screenshot.getBitmap().getHeight()));
+            }
+        }
+
+        prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> {
+            if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+                mMessageContainerController.onScreenshotTaken(screenshot);
+            }
+        });
+
+        if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+            mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+                    mContext.getDrawable(R.drawable.overlay_badge_background),
+                    screenshot.getUserHandle()));
+        }
+        mScreenshotView.setScreenshot(screenshot);
+
+        if (screenshot.getTaskId() >= 0) {
+            mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
+                    new AssistContentRequester.Callback() {
+                        @Override
+                        public void onAssistContentAvailable(AssistContent assistContent) {
+                            screenshot.setContextUrl(assistContent.getWebUri());
+                        }
+                    });
+        }
+
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "setContentView: " + mScreenshotView);
+        }
+        setContentView(mScreenshotView);
+        // ignore system bar insets for the purpose of window layout
+        mWindow.getDecorView().setOnApplyWindowInsetsListener(
+                (v, insets) -> WindowInsets.CONSUMED);
+        mScreenshotHandler.cancelTimeout(); // restarted after animation
+    }
+
+    void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
+        withWindowAttached(() -> {
+            if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+                    && mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
+                mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+                        R.string.screenshot_saving_work_profile_title));
+            } else {
+                mScreenshotView.announceForAccessibility(
+                        mContext.getResources().getString(R.string.screenshot_saving_title));
+            }
+        });
+
+        mScreenshotView.reset();
+
+        if (mScreenshotView.isAttachedToWindow()) {
+            // if we didn't already dismiss for another reason
+            if (!mScreenshotView.isDismissing()) {
+                mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
+                        oldPackageName);
+            }
+            if (DEBUG_WINDOW) {
+                Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
+                        + "(dismissing=" + mScreenshotView.isDismissing() + ")");
+            }
+        }
+
+        mScreenshotView.setPackageName(mPackageName);
+
+        mScreenshotView.updateOrientation(
+                mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+    }
+
     @MainThread
     void takeScreenshotFullscreen(ComponentName topComponent, Consumer<Uri> finisher,
             RequestCallback requestCallback) {
         Assert.isMainThread();
         mCurrentRequestCallback = requestCallback;
-        DisplayMetrics displayMetrics = new DisplayMetrics();
-        getDefaultDisplay().getRealMetrics(displayMetrics);
-        takeScreenshotInternal(
-                topComponent, finisher,
-                new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
+        takeScreenshotInternal(topComponent, finisher, getFullScreenRect());
     }
 
     @MainThread
@@ -416,10 +554,11 @@
         }
 
         boolean showFlash = false;
-        if (!aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
+        if (screenshotScreenBounds == null
+                || !aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
             showFlash = true;
             visibleInsets = Insets.NONE;
-            screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight());
+            screenshotScreenBounds = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
         }
         mCurrentRequestCallback = requestCallback;
         saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
@@ -496,6 +635,9 @@
         // Inflate the screenshot layout
         mScreenshotView = (ScreenshotView)
                 LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+        if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+            mMessageContainerController.setView(mScreenshotView);
+        }
         mScreenshotView.addOnAttachStateChangeListener(
                 new View.OnAttachStateChangeListener() {
                     @Override
@@ -536,6 +678,7 @@
                 setWindowFocusable(false);
             }
         }, mActionExecutor, mFlags);
+        mScreenshotView.setDefaultDisplay(mDisplayTracker.getDefaultDisplayId());
         mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
 
         mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
@@ -553,6 +696,10 @@
             Log.d(TAG, "adding OnComputeInternalInsetsListener");
         }
         mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "setContentView: " + mScreenshotView);
+        }
+        setContentView(mScreenshotView);
     }
 
     /**
@@ -565,7 +712,8 @@
 
         // copy the input Rect, since SurfaceControl.screenshot can mutate it
         Rect screenRect = new Rect(crop);
-        Bitmap screenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY, crop);
+        Bitmap screenshot = mImageCapture.captureDisplay(mDisplayTracker.getDefaultDisplayId(),
+                crop);
 
         if (screenshot == null) {
             Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
@@ -634,7 +782,49 @@
 
         // The window is focusable by default
         setWindowFocusable(true);
+        mScreenshotView.requestFocus();
 
+        enqueueScrollCaptureRequest(owner);
+
+        attachWindow();
+        prepareAnimation(screenRect, showFlash, () -> {
+            if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+                mMessageContainerController.onScreenshotTaken(owner);
+            }
+        });
+
+        if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+            mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+                    mContext.getDrawable(R.drawable.overlay_badge_background), owner));
+        }
+        mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "setContentView: " + mScreenshotView);
+        }
+        setContentView(mScreenshotView);
+        // ignore system bar insets for the purpose of window layout
+        mWindow.getDecorView().setOnApplyWindowInsetsListener(
+                (v, insets) -> WindowInsets.CONSUMED);
+        mScreenshotHandler.cancelTimeout(); // restarted after animation
+    }
+
+    private void prepareAnimation(Rect screenRect, boolean showFlash,
+            Runnable onAnimationComplete) {
+        mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        if (DEBUG_WINDOW) {
+                            Log.d(TAG, "onPreDraw: startAnimation");
+                        }
+                        mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
+                        startAnimation(screenRect, showFlash, onAnimationComplete);
+                        return true;
+                    }
+                });
+    }
+
+    private void enqueueScrollCaptureRequest(UserHandle owner) {
         // Wait until this window is attached to request because it is
         // the reference used to locate the target window (below).
         withWindowAttached(() -> {
@@ -672,33 +862,6 @@
                         }
                     });
         });
-
-        attachWindow();
-        mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        if (DEBUG_WINDOW) {
-                            Log.d(TAG, "onPreDraw: startAnimation");
-                        }
-                        mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
-                        startAnimation(screenRect, showFlash);
-                        return true;
-                    }
-                });
-        if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
-            mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
-                    mContext.getDrawable(R.drawable.overlay_badge_background), owner));
-        }
-        mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
-        if (DEBUG_WINDOW) {
-            Log.d(TAG, "setContentView: " + mScreenshotView);
-        }
-        setContentView(mScreenshotView);
-        // ignore system bar insets for the purpose of window layout
-        mWindow.getDecorView().setOnApplyWindowInsetsListener(
-                (v, insets) -> WindowInsets.CONSUMED);
-        mScreenshotHandler.cancelTimeout(); // restarted after animation
     }
 
     private void requestScrollCapture(UserHandle owner) {
@@ -711,7 +874,7 @@
             mLastScrollCaptureRequest.cancel(true);
         }
         final ListenableFuture<ScrollCaptureResponse> future =
-                mScrollCaptureClient.request(DEFAULT_DISPLAY);
+                mScrollCaptureClient.request(mDisplayTracker.getDefaultDisplayId());
         mLastScrollCaptureRequest = future;
         mLastScrollCaptureRequest.addListener(() ->
                 onScrollCaptureResponseReady(future, owner), mMainExecutor);
@@ -742,7 +905,8 @@
             mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
                 DisplayMetrics displayMetrics = new DisplayMetrics();
                 getDefaultDisplay().getRealMetrics(displayMetrics);
-                Bitmap newScreenshot = mImageCapture.captureDisplay(DEFAULT_DISPLAY,
+                Bitmap newScreenshot = mImageCapture.captureDisplay(
+                        mDisplayTracker.getDefaultDisplayId(),
                         new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
 
                 mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
@@ -806,7 +970,8 @@
                     SCREENSHOT_REMOTE_RUNNER, 0, 0);
             try {
                 WindowManagerGlobal.getWindowManagerService()
-                        .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
+                        .overridePendingAppTransitionRemote(runner,
+                                mDisplayTracker.getDefaultDisplayId());
             } catch (Exception e) {
                 Log.e(TAG, "Error overriding screenshot app transition", e);
             }
@@ -934,13 +1099,22 @@
     /**
      * Starts the animation after taking the screenshot
      */
-    private void startAnimation(Rect screenRect, boolean showFlash) {
+    private void startAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) {
         if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
             mScreenshotAnimation.cancel();
         }
 
         mScreenshotAnimation =
                 mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
+        if (onAnimationComplete != null) {
+            mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    super.onAnimationEnd(animation);
+                    onAnimationComplete.run();
+                }
+            });
+        }
 
         // Play the shutter sound to notify that we've taken a screenshot
         playCameraSound();
@@ -1039,9 +1213,6 @@
 
     private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
         mScreenshotView.setChipIntents(imageData);
-        if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
-            mWorkProfileMessageController.onScreenshotTaken(imageData.owner, mScreenshotView);
-        }
     }
 
     /**
@@ -1144,13 +1315,19 @@
     }
 
     private Display getDefaultDisplay() {
-        return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
+        return mDisplayManager.getDisplay(mDisplayTracker.getDefaultDisplayId());
     }
 
     private boolean allowLongScreenshots() {
         return !mIsLowRamDevice;
     }
 
+    private Rect getFullScreenRect() {
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        getDefaultDisplay().getRealMetrics(displayMetrics);
+        return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+    }
+
     /** Does the aspect ratio of the bitmap with insets removed match the bounds. */
     private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets,
             Rect screenBounds) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
new file mode 100644
index 0000000..e9be88a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -0,0 +1,52 @@
+package com.android.systemui.screenshot
+
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.Insets
+import android.graphics.Rect
+import android.net.Uri
+import android.os.UserHandle
+import android.view.WindowManager.ScreenshotSource
+import android.view.WindowManager.ScreenshotType
+import androidx.annotation.VisibleForTesting
+import com.android.internal.util.ScreenshotRequest
+
+/** ScreenshotData represents the current state of a single screenshot being acquired. */
+data class ScreenshotData(
+    @ScreenshotType var type: Int,
+    @ScreenshotSource var source: Int,
+    /** UserHandle for the owner of the app being screenshotted, if known. */
+    var userHandle: UserHandle?,
+    /** ComponentName of the top-most app in the screenshot. */
+    var topComponent: ComponentName?,
+    var screenBounds: Rect?,
+    var taskId: Int,
+    var insets: Insets,
+    var bitmap: Bitmap?,
+    /** App-provided URL representing the content the user was looking at in the screenshot. */
+    var contextUrl: Uri? = null,
+) {
+    val packageNameString: String
+        get() = if (topComponent == null) "" else topComponent!!.packageName
+
+    companion object {
+        @JvmStatic
+        fun fromRequest(request: ScreenshotRequest): ScreenshotData {
+            return ScreenshotData(
+                request.type,
+                request.source,
+                if (request.userId >= 0) UserHandle.of(request.userId) else null,
+                request.topComponent,
+                request.boundsInScreen,
+                request.taskId,
+                request.insets,
+                request.bitmap,
+            )
+        }
+
+        @VisibleForTesting
+        fun forTesting(): ScreenshotData {
+            return ScreenshotData(0, 0, null, null, null, 0, Insets.NONE, null)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
new file mode 100644
index 0000000..70ea2b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.systemui.screenshot
+
+import android.content.pm.PackageManager
+import android.view.IWindowManager
+import android.view.ViewGroup
+import android.widget.TextView
+import com.android.systemui.R
+import javax.inject.Inject
+
+class ScreenshotDetectionController
+@Inject
+constructor(
+    private val windowManager: IWindowManager,
+    private val packageManager: PackageManager,
+) {
+    /**
+     * Notify potentially listening apps of the screenshot. Return a list of the names of the apps
+     * notified.
+     */
+    fun maybeNotifyOfScreenshot(data: ScreenshotData): List<CharSequence> {
+        // TODO: actually ask the window manager once API is available.
+        return listOf()
+    }
+
+    fun populateView(view: ViewGroup, appNames: List<CharSequence>) {
+        assert(appNames.isNotEmpty())
+
+        val textView: TextView = view.requireViewById(R.id.screenshot_detection_notice_text)
+        if (appNames.size == 1) {
+            textView.text =
+                view.resources.getString(R.string.screenshot_detected_template, appNames[0])
+        } else {
+            textView.text =
+                view.resources.getString(
+                    R.string.screenshot_detected_multiple_template,
+                    appNames[0]
+                )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index c891686..7a62bae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -46,6 +46,8 @@
     SCREENSHOT_SAVED(306),
     @UiEvent(doc = "screenshot failed to save")
     SCREENSHOT_NOT_SAVED(336),
+    @UiEvent(doc = "failed to capture screenshot")
+    SCREENSHOT_CAPTURE_FAILED(1281),
     @UiEvent(doc = "screenshot preview tapped")
     SCREENSHOT_PREVIEW_TAPPED(307),
     @UiEvent(doc = "screenshot edit button tapped")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
index 3a35286..21a7310 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt
@@ -32,12 +32,12 @@
 import android.os.UserHandle
 import android.os.UserManager
 import android.util.Log
-import android.view.Display.DEFAULT_DISPLAY
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.infra.ServiceConnector
 import com.android.systemui.SystemUIService
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
 import java.util.Arrays
 import javax.inject.Inject
@@ -52,6 +52,7 @@
     private val userMgr: UserManager,
     private val atmService: IActivityTaskManager,
     @Background val bgDispatcher: CoroutineDispatcher,
+    private val displayTracker: DisplayTracker
 ) : ScreenshotPolicy {
 
     private val proxyConnector: ServiceConnector<IScreenshotProxy> =
@@ -64,7 +65,7 @@
         )
 
     override fun getDefaultDisplayId(): Int {
-        return DEFAULT_DISPLAY
+        return displayTracker.defaultDisplayId
     }
 
     override suspend fun isManagedProfile(@UserIdInt userId: Int): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 200a7dc..afba7ad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -33,11 +33,11 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -81,7 +81,6 @@
 import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.constraintlayout.widget.ConstraintLayout;
 
@@ -101,8 +100,7 @@
  * Handles the visual elements and animations for the screenshot flow.
  */
 public class ScreenshotView extends FrameLayout implements
-        ViewTreeObserver.OnComputeInternalInsetsListener,
-        WorkProfileMessageController.WorkProfileMessageDisplay {
+        ViewTreeObserver.OnComputeInternalInsetsListener {
 
     interface ScreenshotViewCallback {
         void onUserInteraction();
@@ -122,7 +120,7 @@
     private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
     private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
     private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
-    private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
+    public static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
     private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
     private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
     private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
@@ -134,14 +132,13 @@
     private final AccessibilityManager mAccessibilityManager;
     private final GestureDetector mSwipeDetector;
 
+    private int mDefaultDisplay = Display.DEFAULT_DISPLAY;
     private int mNavMode;
     private boolean mOrientationPortrait;
     private boolean mDirectionLTR;
 
     private ImageView mScrollingScrim;
     private DraggableConstraintLayout mScreenshotStatic;
-    private ViewGroup mMessageContainer;
-    private TextView mMessageContent;
     private ImageView mScreenshotPreview;
     private ImageView mScreenshotBadge;
     private View mScreenshotPreviewBorder;
@@ -166,6 +163,8 @@
 
     private final ArrayList<OverlayActionChip> mSmartChips = new ArrayList<>();
     private PendingInteraction mPendingInteraction;
+    // Should only be set/used if the SCREENSHOT_METADATA flag is set.
+    private ScreenshotData mScreenshotData;
 
     private final InteractionJankMonitor mInteractionJankMonitor;
     private long mDefaultTimeoutOfTimeoutHandler;
@@ -291,8 +290,11 @@
         mDismissButton.getBoundsOnScreen(tmpRect);
         swipeRegion.op(tmpRect, Region.Op.UNION);
 
-        mMessageContainer.findViewById(R.id.message_dismiss_button).getBoundsOnScreen(tmpRect);
-        swipeRegion.op(tmpRect, Region.Op.UNION);
+        View messageDismiss = findViewById(R.id.message_dismiss_button);
+        if (messageDismiss != null) {
+            messageDismiss.getBoundsOnScreen(tmpRect);
+            swipeRegion.op(tmpRect, Region.Op.UNION);
+        }
 
         return swipeRegion;
     }
@@ -323,7 +325,7 @@
 
     private void startInputListening() {
         stopInputListening();
-        mInputMonitor = new InputMonitorCompat("Screenshot", Display.DEFAULT_DISPLAY);
+        mInputMonitor = new InputMonitorCompat("Screenshot", mDefaultDisplay);
         mInputEventReceiver = mInputMonitor.getInputReceiver(
                 Looper.getMainLooper(), Choreographer.getInstance(), ev -> {
                     if (ev instanceof MotionEvent) {
@@ -348,39 +350,11 @@
         }
     }
 
-    /**
-     * Show a notification under the screenshot view indicating that a work profile screenshot has
-     * been taken and which app can be used to view it.
-     *
-     * @param appName The name of the app to use to view screenshots
-     * @param appIcon Optional icon for the relevant files app
-     * @param onDismiss Runnable to be run when the user dismisses this message
-     */
-    @Override
-    public void showWorkProfileMessage(CharSequence appName, @Nullable Drawable appIcon,
-            Runnable onDismiss) {
-        if (appIcon != null) {
-            // Replace the default icon if one is provided.
-            ImageView imageView = mMessageContainer.findViewById(R.id.screenshot_message_icon);
-            imageView.setImageDrawable(appIcon);
-        }
-        mMessageContent.setText(
-                mContext.getString(R.string.screenshot_work_profile_notification, appName));
-        mMessageContainer.setVisibility(VISIBLE);
-        mMessageContainer.findViewById(R.id.message_dismiss_button).setOnClickListener((v) -> {
-            mMessageContainer.setVisibility(View.GONE);
-            onDismiss.run();
-        });
-    }
-
     @Override // View
     protected void onFinishInflate() {
+        super.onFinishInflate();
         mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
         mScreenshotStatic = requireNonNull(findViewById(R.id.screenshot_static));
-        mMessageContainer =
-                requireNonNull(mScreenshotStatic.findViewById(R.id.screenshot_message_container));
-        mMessageContent =
-                requireNonNull(mMessageContainer.findViewById(R.id.screenshot_message_content));
         mScreenshotPreview = requireNonNull(findViewById(R.id.screenshot_preview));
 
         mScreenshotPreviewBorder = requireNonNull(
@@ -470,10 +444,21 @@
         mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
     }
 
+    void setScreenshot(ScreenshotData screenshot) {
+        mScreenshotData = screenshot;
+        setScreenshot(screenshot.getBitmap(), screenshot.getInsets());
+        mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, screenshot.getBitmap(),
+                screenshot.getInsets()));
+    }
+
     void setPackageName(String packageName) {
         mPackageName = packageName;
     }
 
+    void setDefaultDisplay(int displayId) {
+        mDefaultDisplay = displayId;
+    }
+
     void updateInsets(WindowInsets insets) {
         int orientation = mContext.getResources().getConfiguration().orientation;
         mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
@@ -808,9 +793,17 @@
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
             if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
                 prepareSharedTransition();
-                mActionExecutor.launchIntentAsync(
-                        ActionIntentCreator.INSTANCE.createShareIntent(
-                                imageData.uri, imageData.subject),
+
+                Intent shareIntent;
+                if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && mScreenshotData != null
+                        && mScreenshotData.getContextUrl() != null) {
+                    shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithExtraText(
+                            imageData.uri, mScreenshotData.getContextUrl().toString());
+                } else {
+                    shareIntent = ActionIntentCreator.INSTANCE.createShareIntentWithSubject(
+                            imageData.uri, imageData.subject);
+                }
+                mActionExecutor.launchIntentAsync(shareIntent,
                         imageData.shareTransition.get().bundle,
                         imageData.owner.getIdentifier(), false);
             } else {
@@ -1112,6 +1105,7 @@
         mQuickShareChip = null;
         setAlpha(1);
         mScreenshotStatic.setAlpha(1);
+        mScreenshotData = null;
     }
 
     private void startSharedTransition(ActionTransition transition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 35e9f3e..8035d19 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -26,6 +26,7 @@
 import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
 import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED;
 import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
 
 import android.annotation.MainThread;
@@ -54,11 +55,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.FlagListenable.FlagEvent;
+import com.android.systemui.flags.Flags;
 
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -186,8 +188,7 @@
         final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
         RequestCallback callback = new RequestCallbackImpl(replyTo);
 
-        ScreenshotHelper.ScreenshotRequest request =
-                (ScreenshotHelper.ScreenshotRequest) msg.obj;
+        ScreenshotRequest request = (ScreenshotRequest) msg.obj;
 
         handleRequest(request, onSaved, callback);
         return true;
@@ -195,13 +196,14 @@
 
     @MainThread
     @VisibleForTesting
-    void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved,
+    void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
             RequestCallback callback) {
         // If the storage for this user is locked, we have no place to store
         // the screenshot, so skip taking it instead of showing a misleading
         // animation and error notification.
         if (!mUserManager.isUserUnlocked()) {
             Log.w(TAG, "Skipping screenshot because storage is locked!");
+            logFailedRequest(request);
             mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_save_user_locked_text);
             callback.reportError();
@@ -212,6 +214,7 @@
             mBgExecutor.execute(() -> {
                 Log.w(TAG, "Skipping screenshot because an IT admin has disabled "
                         + "screenshots on the device");
+                logFailedRequest(request);
                 String blockedByAdminText = mDevicePolicyManager.getResources().getString(
                         SCREENSHOT_BLOCKED_BY_ADMIN,
                         () -> mContext.getString(R.string.screenshot_blocked_by_admin));
@@ -222,16 +225,46 @@
             return;
         }
 
-        mProcessor.processAsync(request,
-                (r) -> dispatchToController(r, onSaved, callback));
+        if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA)) {
+            Log.d(TAG, "Processing screenshot data");
+            ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
+            try {
+                mProcessor.processAsync(screenshotData,
+                        (data) -> dispatchToController(data, onSaved, callback));
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to process screenshot request!", e);
+                logFailedRequest(request);
+                mNotificationsController.notifyScreenshotError(
+                        R.string.screenshot_failed_to_capture_text);
+                callback.reportError();
+            }
+        } else {
+            try {
+                mProcessor.processAsync(request,
+                        (r) -> dispatchToController(r, onSaved, callback));
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to process screenshot request!", e);
+                logFailedRequest(request);
+                mNotificationsController.notifyScreenshotError(
+                        R.string.screenshot_failed_to_capture_text);
+                callback.reportError();
+            }
+        }
     }
 
-    private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
+    private void dispatchToController(ScreenshotData screenshot,
             Consumer<Uri> uriConsumer, RequestCallback callback) {
+        mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0,
+                screenshot.getPackageNameString());
+        mScreenshot.handleScreenshot(screenshot, uriConsumer, callback);
+    }
 
+    private void dispatchToController(ScreenshotRequest request,
+            Consumer<Uri> uriConsumer, RequestCallback callback) {
         ComponentName topComponent = request.getTopComponent();
-        mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(request.getSource()), 0,
-                topComponent == null ? "" : topComponent.getPackageName());
+        String packageName = topComponent == null ? "" : topComponent.getPackageName();
+        mUiEventLogger.log(
+                ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
 
         switch (request.getType()) {
             case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
@@ -244,28 +277,28 @@
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
                 }
-                Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
-                        request.getBitmapBundle());
+                Bitmap screenshot = request.getBitmap();
                 Rect screenBounds = request.getBoundsInScreen();
                 Insets insets = request.getInsets();
                 int taskId = request.getTaskId();
                 int userId = request.getUserId();
 
-                if (screenshot == null) {
-                    Log.e(TAG, "Got null bitmap from screenshot message");
-                    mNotificationsController.notifyScreenshotError(
-                            R.string.screenshot_failed_to_capture_text);
-                    callback.reportError();
-                } else {
-                    mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
-                            taskId, userId, topComponent, uriConsumer, callback);
-                }
+                mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
+                        taskId, userId, topComponent, uriConsumer, callback);
                 break;
             default:
-                Log.w(TAG, "Invalid screenshot option: " + request.getType());
+                Log.wtf(TAG, "Invalid screenshot option: " + request.getType());
         }
     }
 
+    private void logFailedRequest(ScreenshotRequest request) {
+        ComponentName topComponent = request.getTopComponent();
+        String packageName = topComponent == null ? "" : topComponent.getPackageName();
+        mUiEventLogger.log(
+                ScreenshotEvent.getScreenshotSource(request.getSource()), 0, packageName);
+        mUiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, packageName);
+    }
+
     private static void sendComplete(Messenger target) {
         try {
             if (DEBUG_CALLBACK) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
index 5d7e56f..1b728b8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -23,13 +23,16 @@
 import android.os.UserHandle
 import android.os.UserManager
 import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
 import com.android.systemui.R
 import javax.inject.Inject
 
 /**
- * Handles all the non-UI portions of the work profile first run:
- * - Track whether the user has already dismissed it.
- * - Load the proper icon and app name.
+ * Handles work profile first run, determining whether a first run UI should be shown and populating
+ * that UI if needed.
  */
 class WorkProfileMessageController
 @Inject
@@ -40,10 +43,12 @@
 ) {
 
     /**
-     * Determine if a message should be shown to the user, send message details to messageDisplay if
-     * appropriate.
+     * @return a populated WorkProfileFirstRunData object if a work profile first run message should
+     * be shown
      */
-    fun onScreenshotTaken(userHandle: UserHandle, messageDisplay: WorkProfileMessageDisplay) {
+    fun onScreenshotTaken(userHandle: UserHandle?): WorkProfileFirstRunData? {
+        if (userHandle == null) return null
+
         if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
             var badgedIcon: Drawable? = null
             var label: CharSequence? = null
@@ -62,10 +67,27 @@
             }
 
             // If label wasn't loaded, use a default
-            val badgedLabel =
-                packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
+            return WorkProfileFirstRunData(label ?: defaultFileAppName(), badgedIcon)
+        }
+        return null
+    }
 
-            messageDisplay.showWorkProfileMessage(badgedLabel, badgedIcon) { onMessageDismissed() }
+    /**
+     * Use the provided WorkProfileFirstRunData to populate the work profile first run UI in the
+     * given view.
+     */
+    fun populateView(view: ViewGroup, data: WorkProfileFirstRunData, animateOut: () -> Unit) {
+        if (data.icon != null) {
+            // Replace the default icon if one is provided.
+            val imageView: ImageView = view.requireViewById<ImageView>(R.id.screenshot_message_icon)
+            imageView.setImageDrawable(data.icon)
+        }
+        val messageContent = view.requireViewById<TextView>(R.id.screenshot_message_content)
+        messageContent.text =
+            view.context.getString(R.string.screenshot_work_profile_notification, data.appName)
+        view.requireViewById<View>(R.id.message_dismiss_button).setOnClickListener {
+            animateOut()
+            onMessageDismissed()
         }
     }
 
@@ -89,14 +111,7 @@
 
     private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
 
-    /** UI that can show work profile messages (ScreenshotView in practice) */
-    interface WorkProfileMessageDisplay {
-        /**
-         * Show the given message and icon, calling onDismiss if the user explicitly dismisses the
-         * message.
-         */
-        fun showWorkProfileMessage(text: CharSequence, icon: Drawable?, onDismiss: Runnable)
-    }
+    data class WorkProfileFirstRunData constructor(val appName: CharSequence, val icon: Drawable?)
 
     companion object {
         const val TAG = "WorkProfileMessageCtrl"
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
new file mode 100644
index 0000000..bb7f721
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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.systemui.settings
+
+import android.view.Display
+import java.util.concurrent.Executor
+
+/**
+ * Display tracker for SystemUI.
+ *
+ * This tracker provides async access to display information, as well as callbacks for display
+ * changes.
+ */
+interface DisplayTracker {
+
+    /** The id for the default display for the current SystemUI instance. */
+    val defaultDisplayId: Int
+
+    /** All displays that should be associated with the current SystemUI instance. */
+    val allDisplays: Array<Display>
+
+    /**
+     * Add a [Callback] to be notified of display changes, including additions, removals, and
+     * configuration changes, on a particular [Executor].
+     */
+    fun addDisplayChangeCallback(callback: Callback, executor: Executor)
+
+    /**
+     * Add a [Callback] to be notified of display brightness changes, on a particular [Executor].
+     * This callback will trigger Callback#onDisplayChanged for a display brightness change.
+     */
+    fun addBrightnessChangeCallback(callback: Callback, executor: Executor)
+
+    /** Remove a [Callback] previously added. */
+    fun removeCallback(callback: Callback)
+
+    /** Ćallback for notifying of changes. */
+    interface Callback {
+
+        /** Notifies that a display has been added. */
+        @JvmDefault fun onDisplayAdded(displayId: Int) {}
+
+        /** Notifies that a display has been removed. */
+        @JvmDefault fun onDisplayRemoved(displayId: Int) {}
+
+        /** Notifies a display has been changed */
+        @JvmDefault fun onDisplayChanged(displayId: Int) {}
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
new file mode 100644
index 0000000..5169f88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 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.systemui.settings
+
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.os.Handler
+import android.view.Display
+import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.Assert
+import java.lang.ref.WeakReference
+import java.util.concurrent.Executor
+
+class DisplayTrackerImpl
+internal constructor(
+    val displayManager: DisplayManager,
+    @Background val backgroundHandler: Handler
+) : DisplayTracker {
+    override val defaultDisplayId: Int = Display.DEFAULT_DISPLAY
+    override val allDisplays: Array<Display>
+        get() = displayManager.displays
+
+    @GuardedBy("displayCallbacks")
+    private val displayCallbacks: MutableList<DisplayTrackerDataItem> = ArrayList()
+    @GuardedBy("brightnessCallbacks")
+    private val brightnessCallbacks: MutableList<DisplayTrackerDataItem> = ArrayList()
+
+    @VisibleForTesting
+    val displayChangedListener: DisplayManager.DisplayListener =
+        object : DisplayManager.DisplayListener {
+            override fun onDisplayAdded(displayId: Int) {
+                val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
+                onDisplayAdded(displayId, list)
+            }
+
+            override fun onDisplayRemoved(displayId: Int) {
+                val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
+                onDisplayRemoved(displayId, list)
+            }
+
+            override fun onDisplayChanged(displayId: Int) {
+                val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
+                onDisplayChanged(displayId, list)
+            }
+        }
+
+    @VisibleForTesting
+    val displayBrightnessChangedListener: DisplayManager.DisplayListener =
+        object : DisplayManager.DisplayListener {
+            override fun onDisplayAdded(displayId: Int) {}
+
+            override fun onDisplayRemoved(displayId: Int) {}
+
+            override fun onDisplayChanged(displayId: Int) {
+                val list = synchronized(brightnessCallbacks) { brightnessCallbacks.toList() }
+                onDisplayChanged(displayId, list)
+            }
+        }
+
+    override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) {
+        synchronized(displayCallbacks) {
+            if (displayCallbacks.isEmpty()) {
+                displayManager.registerDisplayListener(displayChangedListener, backgroundHandler)
+            }
+            displayCallbacks.add(DisplayTrackerDataItem(WeakReference(callback), executor))
+        }
+    }
+
+    override fun addBrightnessChangeCallback(
+        callback: DisplayTracker.Callback,
+        executor: Executor
+    ) {
+        synchronized(brightnessCallbacks) {
+            if (brightnessCallbacks.isEmpty()) {
+                displayManager.registerDisplayListener(
+                    displayBrightnessChangedListener,
+                    backgroundHandler,
+                    EVENT_FLAG_DISPLAY_BRIGHTNESS
+                )
+            }
+            brightnessCallbacks.add(DisplayTrackerDataItem(WeakReference(callback), executor))
+        }
+    }
+
+    override fun removeCallback(callback: DisplayTracker.Callback) {
+        synchronized(displayCallbacks) {
+            val changed = displayCallbacks.removeIf { it.sameOrEmpty(callback) }
+            if (changed && displayCallbacks.isEmpty()) {
+                displayManager.unregisterDisplayListener(displayChangedListener)
+            }
+        }
+
+        synchronized(brightnessCallbacks) {
+            val changed = brightnessCallbacks.removeIf { it.sameOrEmpty(callback) }
+            if (changed && brightnessCallbacks.isEmpty()) {
+                displayManager.unregisterDisplayListener(displayBrightnessChangedListener)
+            }
+        }
+    }
+
+    @WorkerThread
+    private fun onDisplayAdded(displayId: Int, list: List<DisplayTrackerDataItem>) {
+        Assert.isNotMainThread()
+
+        notifySubscribers({ onDisplayAdded(displayId) }, list)
+    }
+
+    @WorkerThread
+    private fun onDisplayRemoved(displayId: Int, list: List<DisplayTrackerDataItem>) {
+        Assert.isNotMainThread()
+
+        notifySubscribers({ onDisplayRemoved(displayId) }, list)
+    }
+
+    @WorkerThread
+    private fun onDisplayChanged(displayId: Int, list: List<DisplayTrackerDataItem>) {
+        Assert.isNotMainThread()
+
+        notifySubscribers({ onDisplayChanged(displayId) }, list)
+    }
+
+    private inline fun notifySubscribers(
+        crossinline action: DisplayTracker.Callback.() -> Unit,
+        list: List<DisplayTrackerDataItem>
+    ) {
+        list.forEach {
+            if (it.callback.get() != null) {
+                it.executor.execute { it.callback.get()?.action() }
+            }
+        }
+    }
+
+    private data class DisplayTrackerDataItem(
+        val callback: WeakReference<DisplayTracker.Callback>,
+        val executor: Executor
+    ) {
+        fun sameOrEmpty(other: DisplayTracker.Callback): Boolean {
+            return callback.get()?.equals(other) ?: true
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 1558ac5..287e810 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -62,12 +62,24 @@
     fun removeCallback(callback: Callback)
 
     /**
-     * Ćallback for notifying of changes.
+     * Callback for notifying of changes.
      */
     interface Callback {
 
         /**
+         * Notifies that the current user is being changed.
+         * Override this method to run things while the screen is frozen for the user switch.
+         * Please use {@link #onUserChanged} if the task doesn't need to push the unfreezing of the
+         * screen further. Please be aware that code executed in this callback will lengthen the
+         * user switch duration.
+         */
+        @JvmDefault
+        fun onUserChanging(newUser: Int, userContext: Context) {}
+
+        /**
          * Notifies that the current user has changed.
+         * Override this method to run things after the screen is unfrozen for the user switch.
+         * Please see {@link #onUserChanging} if you need to hide jank.
          */
         @JvmDefault
         fun onUserChanged(newUser: Int, userContext: Context) {}
@@ -78,4 +90,4 @@
         @JvmDefault
         fun onProfilesChanged(profiles: List<@JvmSuppressWildcards UserInfo>) {}
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 61390c5..9f551c6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.settings
 
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
 import android.content.BroadcastReceiver
 import android.content.ContentResolver
 import android.content.Context
@@ -23,6 +25,7 @@
 import android.content.IntentFilter
 import android.content.pm.UserInfo
 import android.os.Handler
+import android.os.IRemoteCallback
 import android.os.UserHandle
 import android.os.UserManager
 import android.util.Log
@@ -34,6 +37,7 @@
 import java.io.PrintWriter
 import java.lang.IllegalStateException
 import java.lang.ref.WeakReference
+import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 import kotlin.properties.ReadWriteProperty
 import kotlin.reflect.KProperty
@@ -56,6 +60,7 @@
 class UserTrackerImpl internal constructor(
     private val context: Context,
     private val userManager: UserManager,
+    private val iActivityManager: IActivityManager,
     private val dumpManager: DumpManager,
     private val backgroundHandler: Handler
 ) : UserTracker, Dumpable, BroadcastReceiver() {
@@ -107,7 +112,6 @@
         setUserIdInternal(startingUser)
 
         val filter = IntentFilter().apply {
-            addAction(Intent.ACTION_USER_SWITCHED)
             addAction(Intent.ACTION_USER_INFO_CHANGED)
             // These get called when a managed profile goes in or out of quiet mode.
             addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
@@ -118,14 +122,13 @@
         }
         context.registerReceiverForAllUsers(this, filter, null /* permission */, backgroundHandler)
 
+        registerUserSwitchObserver()
+
         dumpManager.registerDumpable(TAG, this)
     }
 
     override fun onReceive(context: Context, intent: Intent) {
         when (intent.action) {
-            Intent.ACTION_USER_SWITCHED -> {
-                handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL))
-            }
             Intent.ACTION_USER_INFO_CHANGED,
             Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
             Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
@@ -157,22 +160,43 @@
         return ctx to profiles
     }
 
+    private fun registerUserSwitchObserver() {
+        iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
+            override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
+                backgroundHandler.run {
+                    handleUserSwitching(newUserId)
+                    reply?.sendResult(null)
+                }
+            }
+
+            override fun onUserSwitchComplete(newUserId: Int) {
+                backgroundHandler.run {
+                    handleUserSwitchComplete(newUserId)
+                }
+            }
+        }, TAG)
+    }
+
     @WorkerThread
-    private fun handleSwitchUser(newUser: Int) {
+    private fun handleUserSwitching(newUserId: Int) {
         Assert.isNotMainThread()
-        if (newUser == UserHandle.USER_NULL) {
-            Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent")
-            return
-        }
+        Log.i(TAG, "Switching to user $newUserId")
 
-        if (newUser == userId) return
-        Log.i(TAG, "Switching to user $newUser")
-
-        val (ctx, profiles) = setUserIdInternal(newUser)
-
+        setUserIdInternal(newUserId)
         notifySubscribers {
-            onUserChanged(newUser, ctx)
-            onProfilesChanged(profiles)
+            onUserChanging(newUserId, userContext)
+        }.await()
+    }
+
+    @WorkerThread
+    private fun handleUserSwitchComplete(newUserId: Int) {
+        Assert.isNotMainThread()
+        Log.i(TAG, "Switched to user $newUserId")
+
+        setUserIdInternal(newUserId)
+        notifySubscribers {
+            onUserChanged(newUserId, userContext)
+            onProfilesChanged(userProfiles)
         }
     }
 
@@ -201,17 +225,25 @@
         }
     }
 
-    private inline fun notifySubscribers(crossinline action: UserTracker.Callback.() -> Unit) {
+    private inline fun notifySubscribers(
+            crossinline action: UserTracker.Callback.() -> Unit
+    ): CountDownLatch {
         val list = synchronized(callbacks) {
             callbacks.toList()
         }
+        val latch = CountDownLatch(list.size)
+
         list.forEach {
             if (it.callback.get() != null) {
                 it.executor.execute {
                     it.callback.get()?.action()
+                    latch.countDown()
                 }
+            } else {
+                latch.countDown()
             }
         }
+        return latch
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -258,4 +290,4 @@
     fun sameOrEmpty(other: UserTracker.Callback): Boolean {
         return callback.get()?.equals(other) ?: true
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 5880003..8089d01 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -27,10 +27,10 @@
 import android.database.ContentObserver;
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -49,6 +49,7 @@
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 
@@ -78,19 +79,14 @@
     private final ToggleSlider mControl;
     private final DisplayManager mDisplayManager;
     private final UserTracker mUserTracker;
+    private final DisplayTracker mDisplayTracker;
     private final IVrManager mVrManager;
 
     private final Executor mMainExecutor;
     private final Handler mBackgroundHandler;
     private final BrightnessObserver mBrightnessObserver;
 
-    private final DisplayListener mDisplayListener = new DisplayListener() {
-        @Override
-        public void onDisplayAdded(int displayId) {}
-
-        @Override
-        public void onDisplayRemoved(int displayId) {}
-
+    private final DisplayTracker.Callback mBrightnessListener = new DisplayTracker.Callback() {
         @Override
         public void onDisplayChanged(int displayId) {
             mBackgroundHandler.post(mUpdateSliderRunnable);
@@ -143,14 +139,14 @@
             cr.registerContentObserver(
                     BRIGHTNESS_FOR_VR_FLOAT_URI,
                     false, this, UserHandle.USER_ALL);
-            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+            mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
+                    new HandlerExecutor(mHandler));
         }
 
         public void stopObserving() {
             final ContentResolver cr = mContext.getContentResolver();
             cr.unregisterContentObserver(this);
-            mDisplayManager.unregisterDisplayListener(mDisplayListener);
+            mDisplayTracker.removeCallback(mBrightnessListener);
         }
 
     }
@@ -218,7 +214,7 @@
             automatic = Settings.System.getIntForUser(mContext.getContentResolver(),
                     Settings.System.SCREEN_BRIGHTNESS_MODE,
                     Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
-                    UserHandle.USER_CURRENT);
+                    mUserTracker.getUserId());
             mAutomatic = automatic != Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
         }
     };
@@ -292,6 +288,7 @@
             Context context,
             ToggleSlider control,
             UserTracker userTracker,
+            DisplayTracker displayTracker,
             @Main Executor mainExecutor,
             @Background Handler bgHandler) {
         mContext = context;
@@ -300,6 +297,7 @@
         mMainExecutor = mainExecutor;
         mBackgroundHandler = bgHandler;
         mUserTracker = userTracker;
+        mDisplayTracker = displayTracker;
         mBrightnessObserver = new BrightnessObserver(mHandler);
 
         mDisplayId = mContext.getDisplayId();
@@ -450,6 +448,7 @@
     public static class Factory {
         private final Context mContext;
         private final UserTracker mUserTracker;
+        private final DisplayTracker mDisplayTracker;
         private final Executor mMainExecutor;
         private final Handler mBackgroundHandler;
 
@@ -457,10 +456,12 @@
         public Factory(
                 Context context,
                 UserTracker userTracker,
+                DisplayTracker displayTracker,
                 @Main Executor mainExecutor,
                 @Background Handler bgHandler) {
             mContext = context;
             mUserTracker = userTracker;
+            mDisplayTracker = displayTracker;
             mMainExecutor = mainExecutor;
             mBackgroundHandler = bgHandler;
         }
@@ -471,6 +472,7 @@
                     mContext,
                     toggleSlider,
                     mUserTracker,
+                    mDisplayTracker,
                     mMainExecutor,
                     mBackgroundHandler);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index e208be9..8879501 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -36,6 +36,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 
 import java.util.List;
@@ -49,16 +50,19 @@
     private BrightnessController mBrightnessController;
     private final BrightnessSliderController.Factory mToggleSliderFactory;
     private final UserTracker mUserTracker;
+    private final DisplayTracker mDisplayTracker;
     private final Executor mMainExecutor;
     private final Handler mBackgroundHandler;
 
     @Inject
     public BrightnessDialog(
             UserTracker userTracker,
+            DisplayTracker displayTracker,
             BrightnessSliderController.Factory factory,
             @Main Executor mainExecutor,
             @Background Handler bgHandler) {
         mUserTracker = userTracker;
+        mDisplayTracker = displayTracker;
         mToggleSliderFactory = factory;
         mMainExecutor = mainExecutor;
         mBackgroundHandler = bgHandler;
@@ -106,7 +110,7 @@
         frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
 
         mBrightnessController = new BrightnessController(
-                this, controller, mUserTracker, mMainExecutor, mBackgroundHandler);
+                this, controller, mUserTracker, mDisplayTracker, mMainExecutor, mBackgroundHandler);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
index 2f62e44..e9a1dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
@@ -17,7 +17,9 @@
 package com.android.systemui.settings.dagger;
 
 import android.app.ActivityManager;
+import android.app.IActivityManager;
 import android.content.Context;
+import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.UserManager;
 
@@ -25,6 +27,8 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.settings.DisplayTrackerImpl;
 import com.android.systemui.settings.UserContentResolverProvider;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserFileManager;
@@ -57,15 +61,26 @@
     static UserTracker provideUserTracker(
             Context context,
             UserManager userManager,
+            IActivityManager iActivityManager,
             DumpManager dumpManager,
             @Background Handler handler
     ) {
         int startingUser = ActivityManager.getCurrentUser();
-        UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, dumpManager, handler);
+        UserTrackerImpl tracker = new UserTrackerImpl(context, userManager, iActivityManager,
+                dumpManager, handler);
         tracker.initialize(startingUser);
         return tracker;
     }
 
+    @SysUISingleton
+    @Provides
+    static DisplayTracker provideDisplayTracker(
+            DisplayManager displayManager,
+            @Background Handler handler
+    ) {
+        return new DisplayTrackerImpl(displayManager, handler);
+    }
+
     @Binds
     @IntoMap
     @ClassKey(UserFileManagerImpl.class)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 5011227..b3d31f2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -69,7 +69,8 @@
         }
         return ConstraintsChanges(
             qqsConstraintsChanges = change,
-            qsConstraintsChanges = change
+            qsConstraintsChanges = change,
+            largeScreenConstraintsChanges = change,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index e406be1..197232e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade
 
+import android.animation.Animator
 import android.annotation.IdRes
 import android.app.StatusBarManager
 import android.content.res.Configuration
@@ -45,7 +46,6 @@
 import com.android.systemui.qs.carrier.QSCarrierGroup
 import com.android.systemui.qs.carrier.QSCarrierGroupController
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
 import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -113,7 +113,7 @@
             QQS_HEADER_CONSTRAINT -> "QQS Header"
             QS_HEADER_CONSTRAINT -> "QS Header"
             LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
-            else -> "Unknown state"
+            else -> "Unknown state $this"
         }
     }
 
@@ -176,9 +176,13 @@
     var shadeExpandedFraction = -1f
         set(value) {
             if (field != value) {
+                val oldAlpha = header.alpha
                 header.alpha = ShadeInterpolation.getContentAlpha(value)
                 field = value
-                updateVisibility()
+                if ((oldAlpha == 0f && header.alpha > 0f) ||
+                        (oldAlpha > 0f && header.alpha == 0f)) {
+                    updateVisibility()
+                }
             }
         }
 
@@ -296,21 +300,23 @@
 
     override fun onViewAttached() {
         privacyIconsController.chipVisibilityListener = chipVisibilityListener
+        updateVisibility()
+        updateTransition()
+
         if (header is MotionLayout) {
             header.setOnApplyWindowInsetsListener(insetListener)
             clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
                 val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
                 v.pivotX = newPivot
                 v.pivotY = v.height.toFloat() / 2
+
+                qsCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
             }
         }
 
         dumpManager.registerDumpable(this)
         configurationController.addCallback(configurationControllerListener)
         demoModeController.addCallback(demoModeReceiver)
-
-        updateVisibility()
-        updateTransition()
     }
 
     override fun onViewDetached() {
@@ -335,9 +341,28 @@
                 .setUpdateListener {
                     updateVisibility()
                 }
+                .setListener(endAnimationListener)
                 .start()
     }
 
+    private val endAnimationListener = object : Animator.AnimatorListener {
+        override fun onAnimationCancel(animation: Animator?) {
+            clearListeners()
+        }
+
+        override fun onAnimationEnd(animation: Animator?) {
+            clearListeners()
+        }
+
+        override fun onAnimationRepeat(animation: Animator?) {}
+
+        override fun onAnimationStart(animation: Animator?) {}
+
+        private fun clearListeners() {
+            header.animate().setListener(null).setUpdateListener(null)
+        }
+    }
+
     private fun loadConstraints() {
         if (header is MotionLayout) {
             // Use resources.getXml instead of passing the resource id due to bug b/205018300
@@ -436,15 +461,14 @@
         header as MotionLayout
         if (largeScreenActive) {
             logInstantEvent("Large screen constraints set")
-            header.setTransition(HEADER_TRANSITION_ID)
-            header.transitionToStart()
+            header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
         } else {
             logInstantEvent("Small screen constraints set")
             header.setTransition(HEADER_TRANSITION_ID)
-            header.transitionToStart()
-            updatePosition()
-            updateScrollY()
         }
+        header.jumpToState(header.startState)
+        updatePosition()
+        updateScrollY()
     }
 
     private fun updatePosition() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c0017bee..6c04eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -140,12 +140,16 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
@@ -204,7 +208,6 @@
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
@@ -243,6 +246,7 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import kotlin.Unit;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 @CentralSurfacesComponent.CentralSurfacesScope
@@ -450,6 +454,7 @@
     private float mDownY;
     private int mDisplayTopInset = 0; // in pixels
     private int mDisplayRightInset = 0; // in pixels
+    private int mDisplayLeftInset = 0; // in pixels
     private int mLargeScreenShadeHeaderHeight;
     private int mSplitShadeNotificationsScrimMarginBottom;
 
@@ -697,6 +702,7 @@
     private LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
 
     private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private final KeyguardInteractor mKeyguardInteractor;
     private CoroutineDispatcher mMainDispatcher;
     private boolean mIsOcclusionTransitionRunning = false;
     private int mDreamingToLockscreenTransitionTranslationY;
@@ -826,7 +832,9 @@
             LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
             @Main CoroutineDispatcher mainDispatcher,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            KeyguardLongPressViewModel keyguardLongPressViewModel,
+            KeyguardInteractor keyguardInteractor) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onKeyguardFadingAwayChanged() {
@@ -847,6 +855,7 @@
         mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel;
         mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mKeyguardInteractor = keyguardInteractor;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -999,6 +1008,14 @@
         updateUserSwitcherFlags();
         mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
         mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
+        KeyguardLongPressViewBinder.bind(
+                mView.requireViewById(R.id.keyguard_long_press),
+                keyguardLongPressViewModel,
+                () -> {
+                    onEmptySpaceClick();
+                    return Unit.INSTANCE;
+                },
+                mFalsingManager);
         onFinishInflate();
         keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -2096,7 +2113,17 @@
         }
     }
 
-    public void expandWithoutQs() {
+    /**
+     * Expand shade so that notifications are visible.
+     * Non-split shade: just expanding shade or collapsing QS when they're expanded.
+     * Split shade: only expanding shade, notifications are always visible
+     *
+     * Called when `adb shell cmd statusbar expand-notifications` is executed.
+     */
+    public void expandShadeToNotifications() {
+        if (mSplitShadeEnabled && (isShadeFullyOpen() || isExpanding())) {
+            return;
+        }
         if (isQsExpanded()) {
             flingSettings(0 /* velocity */, FLING_COLLAPSE);
         } else {
@@ -2146,6 +2173,7 @@
         }
         ValueAnimator animator = createHeightAnimator(target, overshootAmount);
         if (expand) {
+            maybeVibrateOnOpening(true /* openingWithTouch */);
             if (expandBecauseOfFalsing && vel < 0) {
                 vel = 0;
             }
@@ -2156,6 +2184,7 @@
                 animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
             }
         } else {
+            mHasVibratedOnOpen = false;
             if (shouldUseDismissingAnimation()) {
                 if (vel == 0) {
                     animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
@@ -2505,7 +2534,7 @@
         if (!mSplitShadeEnabled
                 && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
             mShadeLog.logMotionEvent(event,
-                    "handleQsTouch: QQS touched while shade collapsing, QS tracking disabled");
+                    "handleQsTouch: shade touched while collapsing, QS tracking disabled");
             mQsTracking = false;
         }
         if (!mQsExpandImmediate && mQsTracking) {
@@ -3011,7 +3040,7 @@
             // left bounds can ignore insets, it should always reach the edge of the screen
             return 0;
         } else {
-            return mNotificationStackScrollLayoutController.getLeft();
+            return mNotificationStackScrollLayoutController.getLeft() + mDisplayLeftInset;
         }
     }
 
@@ -3019,7 +3048,7 @@
         if (mIsFullWidth) {
             return mView.getRight() + mDisplayRightInset;
         } else {
-            return mNotificationStackScrollLayoutController.getRight();
+            return mNotificationStackScrollLayoutController.getRight() + mDisplayLeftInset;
         }
     }
 
@@ -3118,20 +3147,15 @@
                     mQsClipBottom,
                     radius,
                     qsVisible && !mSplitShadeEnabled);
+            mKeyguardInteractor.setQuickSettingsVisible(mQsVisible);
         }
         // The padding on this area is large enough that we can use a cheaper clipping strategy
         mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null);
-        if (!qsVisible && mSplitShadeEnabled) {
-            // On the lockscreen when qs isn't visible, we don't want the bounds of the shade to
-            // be visible, otherwise you can see the bounds once swiping up to see bouncer
-            mScrimController.setNotificationsBounds(0, 0, 0, 0);
-        } else {
-            // Increase the height of the notifications scrim when not in split shade
-            // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
-            // in this case they are rendered off-screen
-            final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
-            mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
-        }
+        // Increase the height of the notifications scrim when not in split shade
+        // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
+        // in this case they are rendered off-screen
+        final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
+        mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
 
         if (mSplitShadeEnabled) {
             mKeyguardStatusBarViewController.setNoTopClipping();
@@ -3143,8 +3167,8 @@
 
         // Convert global clipping coordinates to local ones,
         // relative to NotificationStackScrollLayout
-        int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft();
-        int nsslRight = right - mNotificationStackScrollLayoutController.getLeft();
+        int nsslLeft = calculateNsslLeft(left);
+        int nsslRight = calculateNsslRight(right);
         int nsslTop = getNotificationsClippingTopBounds(top);
         int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
         int bottomRadius = mSplitShadeEnabled ? radius : 0;
@@ -3153,6 +3177,22 @@
                 nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
     }
 
+    private int calculateNsslLeft(int nsslLeftAbsolute) {
+        int left = nsslLeftAbsolute - mNotificationStackScrollLayoutController.getLeft();
+        if (mIsFullWidth) {
+            return left;
+        }
+        return left - mDisplayLeftInset;
+    }
+
+    private int calculateNsslRight(int nsslRightAbsolute) {
+        int right = nsslRightAbsolute - mNotificationStackScrollLayoutController.getLeft();
+        if (mIsFullWidth) {
+            return right;
+        }
+        return right - mDisplayLeftInset;
+    }
+
     private int getNotificationsClippingTopBounds(int qsTop) {
         if (mSplitShadeEnabled && mExpandingFromHeadsUp) {
             // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need
@@ -3176,6 +3216,12 @@
     private int calculateQsBottomPosition(float qsExpansionFraction) {
         if (mTransitioningToFullShadeProgress > 0.0f) {
             return mTransitionToFullShadeQSPosition;
+        } else if (mSplitShadeEnabled) {
+            // in split shade - outside lockscreen transition handled above - we simply jump between
+            // two qs expansion values - either shade is closed and qs expansion is 0 or shade is
+            // open and qs expansion is 1
+            int qsBottomTarget = mQs.getDesiredHeight() + mLargeScreenShadeHeaderHeight;
+            return qsExpansionFraction > 0 ? qsBottomTarget : 0;
         } else {
             int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
             int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0;
@@ -3740,7 +3786,8 @@
         //   change due to "unlock hint animation." In this case, fading out the bottom area
         //   would also hide the message that says "swipe to unlock," we don't want to do that.
         float expansionAlpha = MathUtils.map(
-                isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
+                isUnlockHintRunning() ? 0 : KeyguardBouncerConstants.ALPHA_EXPANSION_THRESHOLD, 1f,
+                0f, 1f,
                 getExpandedFraction());
         float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
         alpha *= mBottomAreaShadeAlpha;
@@ -4169,9 +4216,7 @@
     }
 
     private void updateStatusBarIcons() {
-        boolean showIconsWhenExpanded =
-                (isPanelVisibleBecauseOfHeadsUp() || mIsFullWidth)
-                        && getExpandedHeight() < getOpeningHeight();
+        boolean showIconsWhenExpanded = getExpandedHeight() < getOpeningHeight();
         if (showIconsWhenExpanded && isOnKeyguard()) {
             showIconsWhenExpanded = false;
         }
@@ -4238,7 +4283,7 @@
                 && mHeadsUpAppearanceController.shouldBeVisible()) {
             return false;
         }
-        return !mIsFullWidth || !mShowIconsWhenExpanded;
+        return !mShowIconsWhenExpanded;
     }
 
     private void onQsPanelScrollChanged(int scrollY) {
@@ -4550,6 +4595,7 @@
         ipw.print("mDownY="); ipw.println(mDownY);
         ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset);
         ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
+        ipw.print("mDisplayLeftInset="); ipw.println(mDisplayLeftInset);
         ipw.print("mLargeScreenShadeHeaderHeight="); ipw.println(mLargeScreenShadeHeaderHeight);
         ipw.print("mSplitShadeNotificationsScrimMarginBottom=");
         ipw.println(mSplitShadeNotificationsScrimMarginBottom);
@@ -4912,8 +4958,12 @@
             beginJankMonitoring();
         }
         mInitialOffsetOnTouch = expandedHeight;
-        mInitialExpandY = newY;
-        mInitialExpandX = newX;
+        if (!mTracking || isFullyCollapsed()) {
+            mInitialExpandY = newY;
+            mInitialExpandX = newX;
+        } else {
+            mShadeLog.d("not setting mInitialExpandY in startExpandMotion");
+        }
         mInitialTouchFromKeyguard = mKeyguardStateController.isShowing();
         if (startTracking) {
             mTouchSlopExceeded = true;
@@ -5520,7 +5570,7 @@
 
         @Override
         public void flingTopOverscroll(float velocity, boolean open) {
-            // in split shade mode we want to expand/collapse QS only when touch happens within QS
+            // in split shade touches affect QS only when touch happens within QS
             if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) {
                 return;
             }
@@ -5927,6 +5977,7 @@
         Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
         mDisplayTopInset = combinedInsets.top;
         mDisplayRightInset = combinedInsets.right;
+        mDisplayLeftInset = combinedInsets.left;
 
         mNavigationBarBottomHeight = insets.getStableInsetBottom();
         updateMaxHeadsUpTranslation();
@@ -6084,6 +6135,11 @@
 
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
+                    if (mTracking) {
+                        // TODO(b/247126247) fix underlying issue. Should be ACTION_POINTER_DOWN.
+                        mShadeLog.d("Don't intercept down event while already tracking");
+                        return false;
+                    }
                     mCentralSurfaces.userActivity();
                     mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
                     mMinExpandHeight = 0.0f;
@@ -6096,8 +6152,12 @@
                                 + " false");
                         return true;
                     }
-                    mInitialExpandY = y;
-                    mInitialExpandX = x;
+                    if (!mTracking || isFullyCollapsed()) {
+                        mInitialExpandY = y;
+                        mInitialExpandX = x;
+                    } else {
+                        mShadeLog.d("not setting mInitialExpandY in onInterceptTouch");
+                    }
                     mTouchStartedInEmptyArea = !isInContentBounds(x, y);
                     mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
                     mMotionAborted = false;
@@ -6167,6 +6227,11 @@
                             "onTouch: duplicate down event detected... ignoring");
                     return true;
                 }
+                if (mTracking) {
+                    // TODO(b/247126247) fix underlying issue. Should be ACTION_POINTER_DOWN.
+                    mShadeLog.d("Don't handle down event while already tracking");
+                    return true;
+                }
                 mLastTouchDownTime = event.getDownTime();
             }
 
@@ -6286,7 +6351,8 @@
             final float x = event.getX(pointerIndex);
             final float y = event.getY(pointerIndex);
 
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN
+                    || event.getActionMasked() == MotionEvent.ACTION_MOVE) {
                 mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
                 mIgnoreXTouchSlop = true;
             }
@@ -6348,7 +6414,7 @@
                         mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
                     }
                     addMovement(event);
-                    if (!isFullyCollapsed()) {
+                    if (!isFullyCollapsed() && !isOnKeyguard()) {
                         maybeVibrateOnOpening(true /* openingWithTouch */);
                     }
                     float h = y - mInitialExpandY;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 26f8b62..156e4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -19,6 +19,7 @@
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
@@ -52,13 +53,13 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.DumpsysTableLogger;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -72,10 +73,8 @@
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -89,6 +88,7 @@
         Dumpable, ConfigurationListener {
 
     private static final String TAG = "NotificationShadeWindowController";
+    private static final int MAX_STATE_CHANGES_BUFFER_SIZE = 100;
 
     private final Context mContext;
     private final WindowManager mWindowManager;
@@ -108,7 +108,7 @@
     private boolean mHasTopUi;
     private boolean mHasTopUiChanged;
     private float mScreenBrightnessDoze;
-    private final State mCurrentState = new State();
+    private final NotificationShadeWindowState mCurrentState = new NotificationShadeWindowState();
     private OtherwisedCollapsedListener mListener;
     private ForcePluginOpenListener mForcePluginOpenListener;
     private Consumer<Integer> mScrimsVisibilityListener;
@@ -125,6 +125,9 @@
     private int mDeferWindowLayoutParams;
     private boolean mLastKeyguardRotationAllowed;
 
+    private final NotificationShadeWindowState.Buffer mStateBuffer =
+            new NotificationShadeWindowState.Buffer(MAX_STATE_CHANGES_BUFFER_SIZE);
+
     @Inject
     public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
             IActivityManager activityManager, DozeParameters dozeParameters,
@@ -210,8 +213,8 @@
 
     @VisibleForTesting
     void onShadeExpansionFullyChanged(Boolean isExpanded) {
-        if (mCurrentState.mPanelExpanded != isExpanded) {
-            mCurrentState.mPanelExpanded = isExpanded;
+        if (mCurrentState.panelExpanded != isExpanded) {
+            mCurrentState.panelExpanded = isExpanded;
             apply(mCurrentState);
         }
     }
@@ -251,6 +254,7 @@
         mLp.setTitle("NotificationShade");
         mLp.packageName = mContext.getPackageName();
         mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
 
         // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
         // window manager which disables the transient show behavior.
@@ -296,10 +300,10 @@
         mNotificationShadeView.setSystemUiVisibility(vis);
     }
 
-    private void applyKeyguardFlags(State state) {
-        final boolean keyguardOrAod = state.mKeyguardShowing
-                || (state.mDozing && mDozeParameters.getAlwaysOn());
-        if ((keyguardOrAod && !state.mBackdropShowing && !state.mLightRevealScrimOpaque)
+    private void applyKeyguardFlags(NotificationShadeWindowState state) {
+        final boolean keyguardOrAod = state.keyguardShowing
+                || (state.dozing && mDozeParameters.getAlwaysOn());
+        if ((keyguardOrAod && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
                 || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) {
             // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a
             // solid backdrop. Also, show it if we are currently animating between the
@@ -310,15 +314,15 @@
             mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER;
         }
 
-        if (state.mDozing) {
+        if (state.dozing) {
             mLpChanged.privateFlags |= LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
         } else {
             mLpChanged.privateFlags &= ~LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
         }
 
         if (mKeyguardPreferredRefreshRate > 0) {
-            boolean onKeyguard = state.mStatusBarState == StatusBarState.KEYGUARD
-                    && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
+            boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
+                    && !state.keyguardFadingAway && !state.keyguardGoingAway;
             if (onKeyguard
                     && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
                 // both max and min display refresh rate must be set to take effect:
@@ -332,9 +336,9 @@
                     (long) mKeyguardPreferredRefreshRate);
         } else if (mKeyguardMaxRefreshRate > 0) {
             boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled()
-                    && state.mStatusBarState == StatusBarState.KEYGUARD
-                    && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
-            if (state.mDozing || bypassOnKeyguard) {
+                    && state.statusBarState == StatusBarState.KEYGUARD
+                    && !state.keyguardFadingAway && !state.keyguardGoingAway;
+            if (state.dozing || bypassOnKeyguard) {
                 mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardMaxRefreshRate;
             } else {
                 mLpChanged.preferredMaxDisplayRefreshRate = 0;
@@ -343,7 +347,7 @@
                     (long) mLpChanged.preferredMaxDisplayRefreshRate);
         }
 
-        if (state.mBouncerShowing && !isDebuggable()) {
+        if (state.bouncerShowing && !isDebuggable()) {
             mLpChanged.flags |= LayoutParams.FLAG_SECURE;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
@@ -354,8 +358,8 @@
         return Build.IS_DEBUGGABLE;
     }
 
-    private void adjustScreenOrientation(State state) {
-        if (state.mBouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
+    private void adjustScreenOrientation(NotificationShadeWindowState state) {
+        if (state.bouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.dozing) {
             if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
                 mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
             } else {
@@ -366,10 +370,10 @@
         }
     }
 
-    private void applyFocusableFlag(State state) {
-        boolean panelFocusable = state.mNotificationShadeFocusable && state.mPanelExpanded;
-        if (state.mBouncerShowing && (state.mKeyguardOccluded || state.mKeyguardNeedsInput)
-                || ENABLE_REMOTE_INPUT && state.mRemoteInputActive
+    private void applyFocusableFlag(NotificationShadeWindowState state) {
+        boolean panelFocusable = state.notificationShadeFocusable && state.panelExpanded;
+        if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
+                || ENABLE_REMOTE_INPUT && state.remoteInputActive
                 // Make the panel focusable if we're doing the screen off animation, since the light
                 // reveal scrim is drawing in the panel and should consume touch events so that they
                 // don't go to the app behind.
@@ -379,7 +383,7 @@
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
             // Make sure to remove FLAG_ALT_FOCUSABLE_IM when keyguard needs input.
-            if (state.mKeyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
+            if (state.keyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
                 mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
             } else {
                 mLpChanged.flags |= LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -390,19 +394,19 @@
         }
     }
 
-    private void applyForceShowNavigationFlag(State state) {
-        if (state.mPanelExpanded || state.mBouncerShowing
-                || ENABLE_REMOTE_INPUT && state.mRemoteInputActive) {
+    private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
+        if (state.panelExpanded || state.bouncerShowing
+                || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
             mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
         } else {
             mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
         }
     }
 
-    private void applyVisibility(State state) {
+    private void applyVisibility(NotificationShadeWindowState state) {
         boolean visible = isExpanded(state);
         mLogger.logApplyVisibility(visible);
-        if (state.mForcePluginOpen) {
+        if (state.forcePluginOpen) {
             if (mListener != null) {
                 mListener.setWouldOtherwiseCollapse(visible);
             }
@@ -418,16 +422,16 @@
         }
     }
 
-    private boolean isExpanded(State state) {
-        return !state.mForceCollapsed && (state.isKeyguardShowingAndNotOccluded()
-                || state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
-                || state.mHeadsUpShowing
-                || state.mScrimsVisibility != ScrimController.TRANSPARENT)
-                || state.mBackgroundBlurRadius > 0
-                || state.mLaunchingActivity;
+    private boolean isExpanded(NotificationShadeWindowState state) {
+        return !state.forceWindowCollapsed && (state.isKeyguardShowingAndNotOccluded()
+                || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
+                || state.headsUpNotificationShowing
+                || state.scrimsVisibility != ScrimController.TRANSPARENT)
+                || state.backgroundBlurRadius > 0
+                || state.launchingActivityFromNotification;
     }
 
-    private void applyFitsSystemWindows(State state) {
+    private void applyFitsSystemWindows(NotificationShadeWindowState state) {
         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
         if (mNotificationShadeView != null
                 && mNotificationShadeView.getFitsSystemWindows() != fitsSystemWindows) {
@@ -436,21 +440,21 @@
         }
     }
 
-    private void applyUserActivityTimeout(State state) {
+    private void applyUserActivityTimeout(NotificationShadeWindowState state) {
         if (state.isKeyguardShowingAndNotOccluded()
-                && state.mStatusBarState == StatusBarState.KEYGUARD
-                && !state.mQsExpanded) {
-            mLpChanged.userActivityTimeout = state.mBouncerShowing
+                && state.statusBarState == StatusBarState.KEYGUARD
+                && !state.qsExpanded) {
+            mLpChanged.userActivityTimeout = state.bouncerShowing
                     ? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
         } else {
             mLpChanged.userActivityTimeout = -1;
         }
     }
 
-    private void applyInputFeatures(State state) {
+    private void applyInputFeatures(NotificationShadeWindowState state) {
         if (state.isKeyguardShowingAndNotOccluded()
-                && state.mStatusBarState == StatusBarState.KEYGUARD
-                && !state.mQsExpanded && !state.mForceUserActivity) {
+                && state.statusBarState == StatusBarState.KEYGUARD
+                && !state.qsExpanded && !state.forceUserActivity) {
             mLpChanged.inputFeatures |=
                     LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
         } else {
@@ -459,7 +463,7 @@
         }
     }
 
-    private void applyStatusBarColorSpaceAgnosticFlag(State state) {
+    private void applyStatusBarColorSpaceAgnosticFlag(NotificationShadeWindowState state) {
         if (!isExpanded(state)) {
             mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
         } else {
@@ -485,8 +489,8 @@
         applyWindowLayoutParams();
     }
 
-    private void apply(State state) {
-        mLogger.logNewState(state);
+    private void apply(NotificationShadeWindowState state) {
+        logState(state);
         applyKeyguardFlags(state);
         applyFocusableFlag(state);
         applyForceShowNavigationFlag(state);
@@ -515,6 +519,38 @@
         notifyStateChangedCallbacks();
     }
 
+    private void logState(NotificationShadeWindowState state) {
+        mStateBuffer.insert(
+                state.keyguardShowing,
+                state.keyguardOccluded,
+                state.keyguardNeedsInput,
+                state.panelVisible,
+                state.panelExpanded,
+                state.notificationShadeFocusable,
+                state.bouncerShowing,
+                state.keyguardFadingAway,
+                state.keyguardGoingAway,
+                state.qsExpanded,
+                state.headsUpNotificationShowing,
+                state.lightRevealScrimOpaque,
+                state.forceWindowCollapsed,
+                state.forceDozeBrightness,
+                state.forceUserActivity,
+                state.launchingActivityFromNotification,
+                state.mediaBackdropShowing,
+                state.wallpaperSupportsAmbientMode,
+                state.windowNotTouchable,
+                state.componentsForcingTopUi,
+                state.forceOpenTokens,
+                state.statusBarState,
+                state.remoteInputActive,
+                state.forcePluginOpen,
+                state.dozing,
+                state.scrimsVisibility,
+                state.backgroundBlurRadius
+        );
+    }
+
     @Override
     public void notifyStateChangedCallbacks() {
         // Copy callbacks to separate ArrayList to avoid concurrent modification
@@ -523,36 +559,37 @@
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
         for (StatusBarWindowCallback cb : activeCallbacks) {
-            cb.onStateChanged(mCurrentState.mKeyguardShowing,
-                    mCurrentState.mKeyguardOccluded,
-                    mCurrentState.mBouncerShowing,
-                    mCurrentState.mDozing,
-                    mCurrentState.mPanelExpanded);
+            cb.onStateChanged(mCurrentState.keyguardShowing,
+                    mCurrentState.keyguardOccluded,
+                    mCurrentState.bouncerShowing,
+                    mCurrentState.dozing,
+                    mCurrentState.panelExpanded,
+                    mCurrentState.dreaming);
         }
     }
 
-    private void applyModalFlag(State state) {
-        if (state.mHeadsUpShowing) {
+    private void applyModalFlag(NotificationShadeWindowState state) {
+        if (state.headsUpNotificationShowing) {
             mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCH_MODAL;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCH_MODAL;
         }
     }
 
-    private void applyBrightness(State state) {
-        if (state.mForceDozeBrightness) {
+    private void applyBrightness(NotificationShadeWindowState state) {
+        if (state.forceDozeBrightness) {
             mLpChanged.screenBrightness = mScreenBrightnessDoze;
         } else {
             mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
         }
     }
 
-    private void applyHasTopUi(State state) {
-        mHasTopUiChanged = !state.mComponentsForcingTopUi.isEmpty() || isExpanded(state);
+    private void applyHasTopUi(NotificationShadeWindowState state) {
+        mHasTopUiChanged = !state.componentsForcingTopUi.isEmpty() || isExpanded(state);
     }
 
-    private void applyNotTouchable(State state) {
-        if (state.mNotTouchable) {
+    private void applyNotTouchable(NotificationShadeWindowState state) {
+        if (state.windowNotTouchable) {
             mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
         } else {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -574,88 +611,88 @@
 
     @Override
     public void setKeyguardShowing(boolean showing) {
-        mCurrentState.mKeyguardShowing = showing;
+        mCurrentState.keyguardShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setKeyguardOccluded(boolean occluded) {
-        mCurrentState.mKeyguardOccluded = occluded;
+        mCurrentState.keyguardOccluded = occluded;
         apply(mCurrentState);
     }
 
     @Override
     public void setKeyguardNeedsInput(boolean needsInput) {
-        mCurrentState.mKeyguardNeedsInput = needsInput;
+        mCurrentState.keyguardNeedsInput = needsInput;
         apply(mCurrentState);
     }
 
     @Override
     public void setPanelVisible(boolean visible) {
-        if (mCurrentState.mPanelVisible == visible
-                && mCurrentState.mNotificationShadeFocusable == visible) {
+        if (mCurrentState.panelVisible == visible
+                && mCurrentState.notificationShadeFocusable == visible) {
             return;
         }
         mLogger.logShadeVisibleAndFocusable(visible);
-        mCurrentState.mPanelVisible = visible;
-        mCurrentState.mNotificationShadeFocusable = visible;
+        mCurrentState.panelVisible = visible;
+        mCurrentState.notificationShadeFocusable = visible;
         apply(mCurrentState);
     }
 
     @Override
     public void setNotificationShadeFocusable(boolean focusable) {
         mLogger.logShadeFocusable(focusable);
-        mCurrentState.mNotificationShadeFocusable = focusable;
+        mCurrentState.notificationShadeFocusable = focusable;
         apply(mCurrentState);
     }
 
     @Override
     public void setBouncerShowing(boolean showing) {
-        mCurrentState.mBouncerShowing = showing;
+        mCurrentState.bouncerShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setBackdropShowing(boolean showing) {
-        mCurrentState.mBackdropShowing = showing;
+        mCurrentState.mediaBackdropShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setKeyguardFadingAway(boolean keyguardFadingAway) {
-        mCurrentState.mKeyguardFadingAway = keyguardFadingAway;
+        mCurrentState.keyguardFadingAway = keyguardFadingAway;
         apply(mCurrentState);
     }
 
     private void onQsExpansionChanged(Boolean expanded) {
-        mCurrentState.mQsExpanded = expanded;
+        mCurrentState.qsExpanded = expanded;
         apply(mCurrentState);
     }
 
     @Override
     public void setForceUserActivity(boolean forceUserActivity) {
-        mCurrentState.mForceUserActivity = forceUserActivity;
+        mCurrentState.forceUserActivity = forceUserActivity;
         apply(mCurrentState);
     }
 
     @Override
     public void setLaunchingActivity(boolean launching) {
-        mCurrentState.mLaunchingActivity = launching;
+        mCurrentState.launchingActivityFromNotification = launching;
         apply(mCurrentState);
     }
 
     @Override
     public boolean isLaunchingActivity() {
-        return mCurrentState.mLaunchingActivity;
+        return mCurrentState.launchingActivityFromNotification;
     }
 
     @Override
     public void setScrimsVisibility(int scrimsVisibility) {
-        if (scrimsVisibility == mCurrentState.mScrimsVisibility) {
+        if (scrimsVisibility == mCurrentState.scrimsVisibility) {
             return;
         }
         boolean wasExpanded = isExpanded(mCurrentState);
-        mCurrentState.mScrimsVisibility = scrimsVisibility;
+        mCurrentState.scrimsVisibility = scrimsVisibility;
         if (wasExpanded != isExpanded(mCurrentState)) {
             apply(mCurrentState);
         }
@@ -669,31 +706,31 @@
      */
     @Override
     public void setBackgroundBlurRadius(int backgroundBlurRadius) {
-        if (mCurrentState.mBackgroundBlurRadius == backgroundBlurRadius) {
+        if (mCurrentState.backgroundBlurRadius == backgroundBlurRadius) {
             return;
         }
-        mCurrentState.mBackgroundBlurRadius = backgroundBlurRadius;
+        mCurrentState.backgroundBlurRadius = backgroundBlurRadius;
         apply(mCurrentState);
     }
 
     @Override
     public void setHeadsUpShowing(boolean showing) {
-        mCurrentState.mHeadsUpShowing = showing;
+        mCurrentState.headsUpNotificationShowing = showing;
         apply(mCurrentState);
     }
 
     @Override
     public void setLightRevealScrimOpaque(boolean opaque) {
-        if (mCurrentState.mLightRevealScrimOpaque == opaque) {
+        if (mCurrentState.lightRevealScrimOpaque == opaque) {
             return;
         }
-        mCurrentState.mLightRevealScrimOpaque = opaque;
+        mCurrentState.lightRevealScrimOpaque = opaque;
         apply(mCurrentState);
     }
 
     @Override
     public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
-        mCurrentState.mWallpaperSupportsAmbientMode = supportsAmbientMode;
+        mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
         apply(mCurrentState);
     }
 
@@ -701,7 +738,7 @@
      * @param state The {@link StatusBarStateController} of the status bar.
      */
     private void setStatusBarState(int state) {
-        mCurrentState.mStatusBarState = state;
+        mCurrentState.statusBarState = state;
         apply(mCurrentState);
     }
 
@@ -712,13 +749,13 @@
      */
     @Override
     public void setForceWindowCollapsed(boolean force) {
-        mCurrentState.mForceCollapsed = force;
+        mCurrentState.forceWindowCollapsed = force;
         apply(mCurrentState);
     }
 
     @Override
     public void onRemoteInputActive(boolean remoteInputActive) {
-        mCurrentState.mRemoteInputActive = remoteInputActive;
+        mCurrentState.remoteInputActive = remoteInputActive;
         apply(mCurrentState);
     }
 
@@ -728,32 +765,38 @@
      */
     @Override
     public void setForceDozeBrightness(boolean forceDozeBrightness) {
-        if (mCurrentState.mForceDozeBrightness == forceDozeBrightness) {
+        if (mCurrentState.forceDozeBrightness == forceDozeBrightness) {
             return;
         }
-        mCurrentState.mForceDozeBrightness = forceDozeBrightness;
+        mCurrentState.forceDozeBrightness = forceDozeBrightness;
         apply(mCurrentState);
     }
 
     @Override
     public void setDozing(boolean dozing) {
-        mCurrentState.mDozing = dozing;
+        mCurrentState.dozing = dozing;
+        apply(mCurrentState);
+    }
+
+    @Override
+    public void setDreaming(boolean dreaming) {
+        mCurrentState.dreaming = dreaming;
         apply(mCurrentState);
     }
 
     @Override
     public void setForcePluginOpen(boolean forceOpen, Object token) {
         if (forceOpen) {
-            mCurrentState.mForceOpenTokens.add(token);
+            mCurrentState.forceOpenTokens.add(token);
         } else {
-            mCurrentState.mForceOpenTokens.remove(token);
+            mCurrentState.forceOpenTokens.remove(token);
         }
-        final boolean previousForceOpenState = mCurrentState.mForcePluginOpen;
-        mCurrentState.mForcePluginOpen = !mCurrentState.mForceOpenTokens.isEmpty();
-        if (previousForceOpenState != mCurrentState.mForcePluginOpen) {
+        final boolean previousForceOpenState = mCurrentState.forcePluginOpen;
+        mCurrentState.forcePluginOpen = !mCurrentState.forceOpenTokens.isEmpty();
+        if (previousForceOpenState != mCurrentState.forcePluginOpen) {
             apply(mCurrentState);
             if (mForcePluginOpenListener != null) {
-                mForcePluginOpenListener.onChange(mCurrentState.mForcePluginOpen);
+                mForcePluginOpenListener.onChange(mCurrentState.forcePluginOpen);
             }
         }
     }
@@ -763,12 +806,12 @@
      */
     @Override
     public boolean getForcePluginOpen() {
-        return mCurrentState.mForcePluginOpen;
+        return mCurrentState.forcePluginOpen;
     }
 
     @Override
     public void setNotTouchable(boolean notTouchable) {
-        mCurrentState.mNotTouchable = notTouchable;
+        mCurrentState.windowNotTouchable = notTouchable;
         apply(mCurrentState);
     }
 
@@ -777,7 +820,7 @@
      */
     @Override
     public boolean getPanelExpanded() {
-        return mCurrentState.mPanelExpanded;
+        return mCurrentState.panelExpanded;
     }
 
     @Override
@@ -800,11 +843,16 @@
         if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
             mNotificationShadeView.getViewRootImpl().dump("  ", pw);
         }
+        new DumpsysTableLogger(
+                TAG,
+                NotificationShadeWindowState.TABLE_HEADERS,
+                mStateBuffer.toList()
+        ).printTableData(pw);
     }
 
     @Override
     public boolean isShowingWallpaper() {
-        return !mCurrentState.mBackdropShowing;
+        return !mCurrentState.mediaBackdropShowing;
     }
 
     @Override
@@ -834,7 +882,7 @@
      */
     @Override
     public void setKeyguardGoingAway(boolean goingAway) {
-        mCurrentState.mKeyguardGoingAway = goingAway;
+        mCurrentState.keyguardGoingAway = goingAway;
         apply(mCurrentState);
     }
 
@@ -846,87 +894,13 @@
     @Override
     public void setRequestTopUi(boolean requestTopUi, String componentTag) {
         if (requestTopUi) {
-            mCurrentState.mComponentsForcingTopUi.add(componentTag);
+            mCurrentState.componentsForcingTopUi.add(componentTag);
         } else {
-            mCurrentState.mComponentsForcingTopUi.remove(componentTag);
+            mCurrentState.componentsForcingTopUi.remove(componentTag);
         }
         apply(mCurrentState);
     }
 
-    private static class State {
-        boolean mKeyguardShowing;
-        boolean mKeyguardOccluded;
-        boolean mKeyguardNeedsInput;
-        boolean mPanelVisible;
-        boolean mPanelExpanded;
-        boolean mNotificationShadeFocusable;
-        boolean mBouncerShowing;
-        boolean mKeyguardFadingAway;
-        boolean mKeyguardGoingAway;
-        boolean mQsExpanded;
-        boolean mHeadsUpShowing;
-        boolean mLightRevealScrimOpaque;
-        boolean mForceCollapsed;
-        boolean mForceDozeBrightness;
-        boolean mForceUserActivity;
-        boolean mLaunchingActivity;
-        boolean mBackdropShowing;
-        boolean mWallpaperSupportsAmbientMode;
-        boolean mNotTouchable;
-        Set<String> mComponentsForcingTopUi = new HashSet<>();
-        Set<Object> mForceOpenTokens = new HashSet<>();
-
-        /**
-         * The status bar state from {@link CentralSurfaces}.
-         */
-        int mStatusBarState;
-
-        boolean mRemoteInputActive;
-        boolean mForcePluginOpen;
-        boolean mDozing;
-        int mScrimsVisibility;
-        int mBackgroundBlurRadius;
-
-        private boolean isKeyguardShowingAndNotOccluded() {
-            return mKeyguardShowing && !mKeyguardOccluded;
-        }
-
-        @Override
-        public String toString() {
-            return new StringBuilder()
-                    .append("State{")
-                    .append("  mKeyguardShowing=").append(mKeyguardShowing)
-                    .append(", mKeyguardOccluded=").append(mKeyguardOccluded)
-                    .append(", mKeyguardNeedsInput=").append(mKeyguardNeedsInput)
-                    .append(", mPanelVisible=").append(mPanelVisible)
-                    .append(", mPanelExpanded=").append(mPanelExpanded)
-                    .append(", mNotificationShadeFocusable=").append(mNotificationShadeFocusable)
-                    .append(", mBouncerShowing=").append(mBouncerShowing)
-                    .append(", mKeyguardFadingAway=").append(mKeyguardFadingAway)
-                    .append(", mKeyguardGoingAway=").append(mKeyguardGoingAway)
-                    .append(", mQsExpanded=").append(mQsExpanded)
-                    .append(", mHeadsUpShowing=").append(mHeadsUpShowing)
-                    .append(", mLightRevealScrimOpaque=").append(mLightRevealScrimOpaque)
-                    .append(", mForceCollapsed=").append(mForceCollapsed)
-                    .append(", mForceDozeBrightness=").append(mForceDozeBrightness)
-                    .append(", mForceUserActivity=").append(mForceUserActivity)
-                    .append(", mLaunchingActivity=").append(mLaunchingActivity)
-                    .append(", mBackdropShowing=").append(mBackdropShowing)
-                    .append(", mWallpaperSupportsAmbientMode=")
-                    .append(mWallpaperSupportsAmbientMode)
-                    .append(", mNotTouchable=").append(mNotTouchable)
-                    .append(", mComponentsForcingTopUi=").append(mComponentsForcingTopUi)
-                    .append(", mForceOpenTokens=").append(mForceOpenTokens)
-                    .append(", mStatusBarState=").append(mStatusBarState)
-                    .append(", mRemoteInputActive=").append(mRemoteInputActive)
-                    .append(", mForcePluginOpen=").append(mForcePluginOpen)
-                    .append(", mDozing=").append(mDozing)
-                    .append(", mScrimsVisibility=").append(mScrimsVisibility)
-                    .append(", mBackgroundBlurRadius=").append(mBackgroundBlurRadius)
-                    .append('}').toString();
-        }
-    }
-
     private final StateListener mStateListener = new StateListener() {
         @Override
         public void onStateChanged(int newState) {
@@ -937,5 +911,10 @@
         public void onDozingChanged(boolean isDozing) {
             setDozing(isDozing);
         }
+
+        @Override
+        public void onDreamingChanged(boolean isDreaming) {
+            setDreaming(isDreaming);
+        }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
new file mode 100644
index 0000000..fed9b84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2022 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.systemui.shade
+
+import com.android.systemui.dump.DumpsysTableLogger
+import com.android.systemui.dump.Row
+import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.shade.NotificationShadeWindowState.Buffer
+import com.android.systemui.statusbar.StatusBarState
+
+/**
+ * Represents state of shade window, used by [NotificationShadeWindowControllerImpl]. Contains
+ * nested class [Buffer] for pretty table logging in bug reports.
+ */
+class NotificationShadeWindowState(
+    @JvmField var keyguardShowing: Boolean = false,
+    @JvmField var keyguardOccluded: Boolean = false,
+    @JvmField var keyguardNeedsInput: Boolean = false,
+    @JvmField var panelVisible: Boolean = false,
+    /** shade panel is expanded (expansion fraction > 0) */
+    @JvmField var panelExpanded: Boolean = false,
+    @JvmField var notificationShadeFocusable: Boolean = false,
+    @JvmField var bouncerShowing: Boolean = false,
+    @JvmField var keyguardFadingAway: Boolean = false,
+    @JvmField var keyguardGoingAway: Boolean = false,
+    @JvmField var qsExpanded: Boolean = false,
+    @JvmField var headsUpNotificationShowing: Boolean = false,
+    @JvmField var lightRevealScrimOpaque: Boolean = false,
+    @JvmField var forceWindowCollapsed: Boolean = false,
+    @JvmField var forceDozeBrightness: Boolean = false,
+    // TODO: forceUserActivity seems to be unused, delete?
+    @JvmField var forceUserActivity: Boolean = false,
+    @JvmField var launchingActivityFromNotification: Boolean = false,
+    @JvmField var mediaBackdropShowing: Boolean = false,
+    @JvmField var wallpaperSupportsAmbientMode: Boolean = false,
+    @JvmField var windowNotTouchable: Boolean = false,
+    @JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(),
+    @JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(),
+    /** one of [StatusBarState] */
+    @JvmField var statusBarState: Int = 0,
+    @JvmField var remoteInputActive: Boolean = false,
+    @JvmField var forcePluginOpen: Boolean = false,
+    @JvmField var dozing: Boolean = false,
+    @JvmField var dreaming: Boolean = false,
+    @JvmField var scrimsVisibility: Int = 0,
+    @JvmField var backgroundBlurRadius: Int = 0,
+) {
+
+    fun isKeyguardShowingAndNotOccluded(): Boolean {
+        return keyguardShowing && !keyguardOccluded
+    }
+
+    /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
+    val asStringList: List<String> by lazy {
+        listOf(
+            keyguardShowing.toString(),
+            keyguardOccluded.toString(),
+            keyguardNeedsInput.toString(),
+            panelVisible.toString(),
+            panelExpanded.toString(),
+            notificationShadeFocusable.toString(),
+            bouncerShowing.toString(),
+            keyguardFadingAway.toString(),
+            keyguardGoingAway.toString(),
+            qsExpanded.toString(),
+            headsUpNotificationShowing.toString(),
+            lightRevealScrimOpaque.toString(),
+            forceWindowCollapsed.toString(),
+            forceDozeBrightness.toString(),
+            forceUserActivity.toString(),
+            launchingActivityFromNotification.toString(),
+            mediaBackdropShowing.toString(),
+            wallpaperSupportsAmbientMode.toString(),
+            windowNotTouchable.toString(),
+            componentsForcingTopUi.toString(),
+            forceOpenTokens.toString(),
+            StatusBarState.toString(statusBarState),
+            remoteInputActive.toString(),
+            forcePluginOpen.toString(),
+            dozing.toString(),
+            scrimsVisibility.toString(),
+            backgroundBlurRadius.toString()
+        )
+    }
+
+    /**
+     * [RingBuffer] to store [NotificationShadeWindowState]. After the buffer is full, it will
+     * recycle old events.
+     */
+    class Buffer(capacity: Int) {
+
+        private val buffer = RingBuffer(capacity) { NotificationShadeWindowState() }
+
+        /** Insert a new element in the buffer. */
+        fun insert(
+            keyguardShowing: Boolean,
+            keyguardOccluded: Boolean,
+            keyguardNeedsInput: Boolean,
+            panelVisible: Boolean,
+            panelExpanded: Boolean,
+            notificationShadeFocusable: Boolean,
+            bouncerShowing: Boolean,
+            keyguardFadingAway: Boolean,
+            keyguardGoingAway: Boolean,
+            qsExpanded: Boolean,
+            headsUpShowing: Boolean,
+            lightRevealScrimOpaque: Boolean,
+            forceCollapsed: Boolean,
+            forceDozeBrightness: Boolean,
+            forceUserActivity: Boolean,
+            launchingActivity: Boolean,
+            backdropShowing: Boolean,
+            wallpaperSupportsAmbientMode: Boolean,
+            notTouchable: Boolean,
+            componentsForcingTopUi: MutableSet<String>,
+            forceOpenTokens: MutableSet<Any>,
+            statusBarState: Int,
+            remoteInputActive: Boolean,
+            forcePluginOpen: Boolean,
+            dozing: Boolean,
+            scrimsVisibility: Int,
+            backgroundBlurRadius: Int,
+        ) {
+            buffer.advance().apply {
+                this.keyguardShowing = keyguardShowing
+                this.keyguardOccluded = keyguardOccluded
+                this.keyguardNeedsInput = keyguardNeedsInput
+                this.panelVisible = panelVisible
+                this.panelExpanded = panelExpanded
+                this.notificationShadeFocusable = notificationShadeFocusable
+                this.bouncerShowing = bouncerShowing
+                this.keyguardFadingAway = keyguardFadingAway
+                this.keyguardGoingAway = keyguardGoingAway
+                this.qsExpanded = qsExpanded
+                this.headsUpNotificationShowing = headsUpShowing
+                this.lightRevealScrimOpaque = lightRevealScrimOpaque
+                this.forceWindowCollapsed = forceCollapsed
+                this.forceDozeBrightness = forceDozeBrightness
+                this.forceUserActivity = forceUserActivity
+                this.launchingActivityFromNotification = launchingActivity
+                this.mediaBackdropShowing = backdropShowing
+                this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode
+                this.windowNotTouchable = notTouchable
+                this.componentsForcingTopUi.clear()
+                this.componentsForcingTopUi.addAll(componentsForcingTopUi)
+                this.forceOpenTokens.clear()
+                this.forceOpenTokens.addAll(forceOpenTokens)
+                this.statusBarState = statusBarState
+                this.remoteInputActive = remoteInputActive
+                this.forcePluginOpen = forcePluginOpen
+                this.dozing = dozing
+                this.scrimsVisibility = scrimsVisibility
+                this.backgroundBlurRadius = backgroundBlurRadius
+            }
+        }
+
+        /**
+         * Returns the content of the buffer (sorted from latest to newest).
+         *
+         * @see [NotificationShadeWindowState.asStringList]
+         */
+        fun toList(): List<Row> {
+            return buffer.asSequence().map { it.asStringList }.toList()
+        }
+    }
+
+    companion object {
+        /** Headers for dumping a table using [DumpsysTableLogger]. */
+        @JvmField
+        val TABLE_HEADERS =
+            listOf(
+                "keyguardShowing",
+                "keyguardOccluded",
+                "keyguardNeedsInput",
+                "panelVisible",
+                "panelExpanded",
+                "notificationShadeFocusable",
+                "bouncerShowing",
+                "keyguardFadingAway",
+                "keyguardGoingAway",
+                "qsExpanded",
+                "headsUpShowing",
+                "lightRevealScrimOpaque",
+                "forceCollapsed",
+                "forceDozeBrightness",
+                "forceUserActivity",
+                "launchingActivity",
+                "backdropShowing",
+                "wallpaperSupportsAmbientMode",
+                "notTouchable",
+                "componentsForcingTopUi",
+                "forceOpenTokens",
+                "statusBarState",
+                "remoteInputActive",
+                "forcePluginOpen",
+                "dozing",
+                "scrimsVisibility",
+                "backgroundBlurRadius"
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 7ed6e3e..60fa865 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -160,12 +160,10 @@
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
-        if (featureFlags.isEnabled(Flags.MODERN_BOUNCER)) {
-            KeyguardBouncerViewBinder.bind(
-                    mView.findViewById(R.id.keyguard_bouncer_container),
-                    keyguardBouncerViewModel,
-                    keyguardBouncerComponentFactory);
-        }
+        KeyguardBouncerViewBinder.bind(
+                mView.findViewById(R.id.keyguard_bouncer_container),
+                keyguardBouncerViewModel,
+                keyguardBouncerComponentFactory);
 
         if (featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION)) {
             collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 85b259e..de02115 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentService
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.plugins.qs.QSContainerController
@@ -54,6 +55,7 @@
     private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController,
     private val shadeExpansionStateManager: ShadeExpansionStateManager,
     private val featureFlags: FeatureFlags,
+    private val fragmentService: FragmentService,
     @Main private val delayableExecutor: DelayableExecutor
 ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
 
@@ -128,6 +130,7 @@
         mView.setInsetsChangedListener(delayedInsetSetter)
         mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) }
         mView.setConfigurationChangedListener { updateResources() }
+        fragmentService.getFragmentHostManager(mView).addTagListener(QS.TAG, mView)
     }
 
     override fun onViewDetached() {
@@ -136,6 +139,7 @@
         mView.removeOnInsetsChangedListener()
         mView.removeQSFragmentAttachedListener()
         mView.setConfigurationChangedListener(null)
+        fragmentService.getFragmentHostManager(mView).removeTagListener(QS.TAG, mView)
     }
 
     fun updateResources() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 02316b7..f73dde6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -29,7 +29,6 @@
 import androidx.constraintlayout.widget.ConstraintSet;
 
 import com.android.systemui.R;
-import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
@@ -133,18 +132,6 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        FragmentHostManager.get(this).addTagListener(QS.TAG, this);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        FragmentHostManager.get(this).removeTagListener(QS.TAG, this);
-    }
-
-    @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mInsetsChangedListener.accept(insets);
         return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index 49709a8..c8b6a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -7,6 +7,7 @@
 per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
 per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
 per-file *ShadeHeader* = kozynski@google.com, asc@google.com
+per-file *Shade* = justinweir@google.com
 
 per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
 per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
@@ -14,4 +15,4 @@
 per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com
 
 per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
-per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
\ No newline at end of file
+per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index db70065..b42bdaa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -19,7 +19,6 @@
 import android.hardware.display.AmbientDisplayConfiguration
 import android.os.PowerManager
 import android.os.SystemClock
-import android.os.UserHandle
 import android.provider.Settings
 import android.view.GestureDetector
 import android.view.MotionEvent
@@ -29,6 +28,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
 import com.android.systemui.tuner.TunerService
@@ -54,6 +54,7 @@
         private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
         private val statusBarStateController: StatusBarStateController,
         private val shadeLogger: ShadeLogger,
+        userTracker: UserTracker,
         tunerService: TunerService,
         dumpManager: DumpManager
 ) : GestureDetector.SimpleOnGestureListener(), Dumpable {
@@ -65,10 +66,10 @@
             when (key) {
                 Settings.Secure.DOZE_DOUBLE_TAP_GESTURE ->
                     doubleTapEnabled = ambientDisplayConfiguration.doubleTapGestureEnabled(
-                            UserHandle.USER_CURRENT)
+                            userTracker.userId)
                 Settings.Secure.DOZE_TAP_SCREEN_GESTURE ->
                     singleTapEnabled = ambientDisplayConfiguration.tapGestureEnabled(
-                            UserHandle.USER_CURRENT)
+                            userTracker.userId)
             }
         }
         tunerService.addTunable(tunable,
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
new file mode 100644
index 0000000..ab0d6e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.systemui.smartspace.config
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.BcSmartspaceConfigPlugin
+
+class BcSmartspaceConfigProvider(private val featureFlags: FeatureFlags) :
+    BcSmartspaceConfigPlugin {
+    override val isDefaultDateWeatherDisabled: Boolean
+        get() = featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index b02a45a..641131e 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -18,7 +18,6 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
-import com.android.systemui.smartspace.filters.LockscreenAndDreamTargetFilter
 import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
 import dagger.Binds
 import dagger.BindsOptionalOf
@@ -35,11 +34,6 @@
         const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin"
 
         /**
-         * The lockscreen smartspace target filter.
-         */
-        const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter"
-
-        /**
          * The dream smartspace target filter.
          */
         const val DREAM_SMARTSPACE_TARGET_FILTER = "dream_smartspace_target_filter"
@@ -48,6 +42,16 @@
          * The precondition for dream smartspace
          */
         const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"
+
+        /**
+         * The BcSmartspaceDataPlugin for the standalone date (+alarm+dnd).
+         */
+        const val DATE_SMARTSPACE_DATA_PLUGIN = "date_smartspace_data_plugin"
+
+        /**
+         * The BcSmartspaceDataPlugin for the standalone weather.
+         */
+        const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
     }
 
     @BindsOptionalOf
@@ -59,12 +63,6 @@
     abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
 
     @Binds
-    @Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER)
-    abstract fun provideLockscreenSmartspaceTargetFilter(
-        filter: LockscreenAndDreamTargetFilter?
-    ): SmartspaceTargetFilter?
-
-    @Binds
     @Named(DREAM_SMARTSPACE_PRECONDITION)
     abstract fun bindSmartspacePrecondition(
         lockscreenPrecondition: LockscreenPrecondition?
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt b/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt
index 1302ec9..88e8ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt
@@ -15,8 +15,6 @@
  */
 package com.android.systemui.smartspace.preconditions
 
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.util.concurrency.Execution
@@ -24,11 +22,9 @@
 
 /**
  * {@link LockscreenPrecondition} covers the conditions that must be met before Smartspace can be
- * used over lockscreen. These conditions include the device being provisioned with a setup user
- * and the Smartspace feature flag enabled.
+ * used over lockscreen. These conditions include the device being provisioned with a setup user.
  */
 class LockscreenPrecondition @Inject constructor(
-    private val featureFlags: FeatureFlags,
     private val deviceProvisionedController: DeviceProvisionedController,
     private val execution: Execution
 ) : SmartspacePrecondition {
@@ -90,6 +86,6 @@
 
     override fun conditionsMet(): Boolean {
         execution.assertIsMainThread()
-        return featureFlags.isEnabled(Flags.SMARTSPACE) && deviceReady
+        return deviceReady
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
index 87c12c2..6577cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedButton.java
@@ -20,10 +20,21 @@
 import android.util.AttributeSet;
 import android.widget.Button;
 
+import com.android.systemui.animation.LaunchableView;
+import com.android.systemui.animation.LaunchableViewDelegate;
+
+import kotlin.Unit;
+
 /**
  * A Button which doesn't have overlapping drawing commands
  */
-public class AlphaOptimizedButton extends Button {
+public class AlphaOptimizedButton extends Button implements LaunchableView {
+    private LaunchableViewDelegate mDelegate = new LaunchableViewDelegate(this,
+            (visibility) -> {
+                super.setVisibility(visibility);
+                return Unit.INSTANCE;
+            });
+
     public AlphaOptimizedButton(Context context) {
         super(context);
     }
@@ -45,4 +56,14 @@
     public boolean hasOverlappingRendering() {
         return false;
     }
+
+    @Override
+    public void setShouldBlockVisibilityChanges(boolean block) {
+        mDelegate.setShouldBlockVisibilityChanges(block);
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        mDelegate.setVisibility(visibility);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 584a382..f2e729d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -20,11 +20,8 @@
 import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT;
 import static android.inputmethodservice.InputMethodService.IME_INVISIBLE;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
-import static com.android.systemui.statusbar.phone.CentralSurfacesImpl.ONLY_CORE_APPS;
-
 import android.annotation.Nullable;
 import android.app.ITransientNotificationCallback;
 import android.app.StatusBarManager;
@@ -40,17 +37,19 @@
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
-import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.IUdfpsHbmListener;
 import android.inputmethodservice.InputMethodService.BackDispositionMode;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -60,6 +59,7 @@
 import android.view.WindowInsetsController.Behavior;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IAddTileResultCallback;
@@ -70,6 +70,7 @@
 import com.android.internal.util.GcUtils;
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.dump.DumpHandler;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
 import com.android.systemui.statusbar.policy.CallbackController;
@@ -89,8 +90,7 @@
  * are coalesced, note that they are all idempotent.
  */
 public class CommandQueue extends IStatusBar.Stub implements
-        CallbackController<Callbacks>,
-        DisplayManager.DisplayListener {
+        CallbackController<Callbacks> {
     private static final String TAG = CommandQueue.class.getSimpleName();
 
     private static final int INDEX_MASK = 0xffff;
@@ -168,6 +168,7 @@
     private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
     private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
     private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
+    private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -191,6 +192,7 @@
     private ProtoTracer mProtoTracer;
     private final @Nullable CommandRegistry mRegistry;
     private final @Nullable DumpHandler mDumpHandler;
+    private final @Nullable DisplayTracker mDisplayTracker;
 
     /**
      * These methods are called back on the main thread.
@@ -350,7 +352,7 @@
         }
 
         /**
-         * @see DisplayManager.DisplayListener#onDisplayRemoved(int)
+         * @see DisplayTracker.Callback#onDisplayRemoved(int)
          */
         default void onDisplayRemoved(int displayId) {
         }
@@ -492,14 +494,21 @@
          * @see IStatusBar#enterStageSplitFromRunningApp
          */
         default void enterStageSplitFromRunningApp(boolean leftOrTop) {}
+
+        /**
+         * @see IStatusBar#showMediaOutputSwitcher
+         */
+        default void showMediaOutputSwitcher(String packageName) {}
     }
 
-    public CommandQueue(Context context) {
-        this(context, null, null, null);
+    @VisibleForTesting
+    public CommandQueue(Context context, DisplayTracker displayTracker) {
+        this(context, displayTracker, null, null, null);
     }
 
     public CommandQueue(
             Context context,
+            DisplayTracker displayTracker,
             ProtoTracer protoTracer,
             CommandRegistry registry,
             DumpHandler dumpHandler
@@ -507,36 +516,30 @@
         mProtoTracer = protoTracer;
         mRegistry = registry;
         mDumpHandler = dumpHandler;
-        context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler);
+        mDisplayTracker = displayTracker;
+        mDisplayTracker.addDisplayChangeCallback(new DisplayTracker.Callback() {
+            @Override
+            public void onDisplayRemoved(int displayId) {
+                synchronized (mLock) {
+                    mDisplayDisabled.remove(displayId);
+                }
+                // This callback is registered with {@link #mHandler} that already posts to run
+                // on main thread, so it is safe to dispatch directly.
+                for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+                    mCallbacks.get(i).onDisplayRemoved(displayId);
+                }
+            }
+        }, new HandlerExecutor(mHandler));
         // We always have default display.
-        setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE);
+        setDisabled(mDisplayTracker.getDefaultDisplayId(), DISABLE_NONE, DISABLE2_NONE);
     }
 
-    @Override
-    public void onDisplayAdded(int displayId) { }
-
-    @Override
-    public void onDisplayRemoved(int displayId) {
-        synchronized (mLock) {
-            mDisplayDisabled.remove(displayId);
-        }
-        // This callback is registered with {@link #mHandler} that already posts to run on main
-        // thread, so it is safe to dispatch directly.
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            mCallbacks.get(i).onDisplayRemoved(displayId);
-        }
-    }
-
-    @Override
-    public void onDisplayChanged(int displayId) { }
-
     // TODO(b/118592525): add multi-display support if needed.
     public boolean panelsEnabled() {
-        final int disabled1 = getDisabled1(DEFAULT_DISPLAY);
-        final int disabled2 = getDisabled2(DEFAULT_DISPLAY);
+        final int disabled1 = getDisabled1(mDisplayTracker.getDefaultDisplayId());
+        final int disabled2 = getDisabled2(mDisplayTracker.getDefaultDisplayId());
         return (disabled1 & StatusBarManager.DISABLE_EXPAND) == 0
-                && (disabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0
-                && !ONLY_CORE_APPS;
+                && (disabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0;
     }
 
     @Override
@@ -1262,6 +1265,19 @@
     }
 
     @Override
+    public void showMediaOutputSwitcher(String packageName) {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException("Call only allowed from system server.");
+        }
+        synchronized (mLock) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = packageName;
+            mHandler.obtainMessage(MSG_SHOW_MEDIA_OUTPUT_SWITCHER, args).sendToTarget();
+        }
+    }
+
+    @Override
     public void requestAddTile(
             @NonNull ComponentName componentName,
             @NonNull CharSequence appName,
@@ -1777,6 +1793,13 @@
                         mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
                     }
                     break;
+                case MSG_SHOW_MEDIA_OUTPUT_SWITCHER:
+                    args = (SomeArgs) msg.obj;
+                    String clientPackageName = (String) args.arg1;
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).showMediaOutputSwitcher(clientPackageName);
+                    }
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 63179da..5adb58b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -77,6 +77,12 @@
      */
     public static void fadeOut(View view, float fadeOutAmount, boolean remap) {
         view.animate().cancel();
+
+        // Don't fade out if already not visible.
+        if (view.getAlpha() == 0.0f) {
+            return;
+        }
+
         if (fadeOutAmount == 1.0f && view.getVisibility() != View.GONE) {
             view.setVisibility(View.INVISIBLE);
         } else if (view.getVisibility() == View.INVISIBLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java
new file mode 100644
index 0000000..a797d4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java
@@ -0,0 +1,40 @@
+/*
+ * 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.systemui.statusbar;
+
+import android.content.BroadcastReceiver;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Module for {@link com.android.systemui.KeyboardShortcutsReceiver}.
+ */
+@Module
+public abstract class KeyboardShortcutsModule {
+
+    /**
+     *
+     */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyboardShortcutsReceiver.class)
+    public abstract BroadcastReceiver bindKeyboardShortcutsReceiver(
+            KeyboardShortcutsReceiver broadcastReceiver);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 006b552..6481855 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -43,6 +43,7 @@
 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
 import static com.android.systemui.plugins.log.LogLevel.ERROR;
 
+import android.app.AlarmManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -98,6 +99,7 @@
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.wakelock.SettableWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -127,10 +129,8 @@
     private static final String TAG = "KeyguardIndication";
     private static final boolean DEBUG_CHARGING_SPEED = false;
 
-    private static final int MSG_HIDE_TRANSIENT = 1;
-    private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
-    private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3;
-    private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 4;
+    private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1;
+    private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 2;
     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
     public static final long DEFAULT_HIDE_DELAY_MS =
             3500 + KeyguardIndicationTextView.Y_IN_DURATION;
@@ -212,6 +212,11 @@
     };
     private boolean mFaceLockedOutThisAuthSession;
 
+    // Use AlarmTimeouts to guarantee that the events are handled even if scheduled and
+    // triggered while the device is asleep
+    private final AlarmTimeout mHideTransientMessageHandler;
+    private final AlarmTimeout mHideBiometricMessageHandler;
+
     /**
      * Creates a new KeyguardIndicationController and registers callbacks.
      */
@@ -238,7 +243,9 @@
             AccessibilityManager accessibilityManager,
             FaceHelpMessageDeferral faceHelpMessageDeferral,
             KeyguardLogger keyguardLogger,
-            AlternateBouncerInteractor alternateBouncerInteractor) {
+            AlternateBouncerInteractor alternateBouncerInteractor,
+            AlarmManager alarmManager
+    ) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mDevicePolicyManager = devicePolicyManager;
@@ -273,17 +280,26 @@
         mHandler = new Handler(mainLooper) {
             @Override
             public void handleMessage(Message msg) {
-                if (msg.what == MSG_HIDE_TRANSIENT) {
-                    hideTransientIndication();
-                } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
+                if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
                     showActionToUnlock();
-                } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) {
-                    hideBiometricMessage();
                 } else if (msg.what == MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON) {
                     mBiometricErrorMessageToShowOnScreenOn = null;
                 }
             }
         };
+
+        mHideTransientMessageHandler = new AlarmTimeout(
+                alarmManager,
+                this::hideTransientIndication,
+                TAG,
+                mHandler
+        );
+        mHideBiometricMessageHandler = new AlarmTimeout(
+                alarmManager,
+                this::hideBiometricMessage,
+                TAG,
+                mHandler
+        );
     }
 
     /** Call this after construction to finish setting up the instance. */
@@ -335,6 +351,8 @@
      */
     public void destroy() {
         mHandler.removeCallbacksAndMessages(null);
+        mHideBiometricMessageHandler.cancel();
+        mHideTransientMessageHandler.cancel();
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
     }
 
@@ -679,7 +697,7 @@
         if (visible) {
             // If this is called after an error message was already shown, we should not clear it.
             // Otherwise the error message won't be shown
-            if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
+            if (!mHideTransientMessageHandler.isScheduled()) {
                 hideTransientIndication();
             }
             updateDeviceEntryIndication(false);
@@ -727,16 +745,14 @@
      * Hides transient indication in {@param delayMs}.
      */
     public void hideTransientIndicationDelayed(long delayMs) {
-        mHandler.sendMessageDelayed(
-                mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs);
+        mHideTransientMessageHandler.schedule(delayMs, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
     }
 
     /**
      * Hides biometric indication in {@param delayMs}.
      */
     public void hideBiometricMessageDelayed(long delayMs) {
-        mHandler.sendMessageDelayed(
-                mHandler.obtainMessage(MSG_HIDE_BIOMETRIC_MESSAGE), delayMs);
+        mHideBiometricMessageHandler.schedule(delayMs, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
     }
 
     /**
@@ -751,7 +767,6 @@
      */
     private void showTransientIndication(CharSequence transientIndication) {
         mTransientIndication = transientIndication;
-        mHandler.removeMessages(MSG_HIDE_TRANSIENT);
         hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
 
         updateTransient();
@@ -777,7 +792,6 @@
         mBiometricMessageFollowUp = biometricMessageFollowUp;
 
         mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
-        mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
         hideBiometricMessageDelayed(
                 mBiometricMessageFollowUp != null
                         ? IMPORTANT_MSG_MIN_DURATION * 2
@@ -791,7 +805,7 @@
         if (mBiometricMessage != null || mBiometricMessageFollowUp != null) {
             mBiometricMessage = null;
             mBiometricMessageFollowUp = null;
-            mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE);
+            mHideBiometricMessageHandler.cancel();
             updateBiometricMessage();
         }
     }
@@ -802,7 +816,7 @@
     public void hideTransientIndication() {
         if (mTransientIndication != null) {
             mTransientIndication = null;
-            mHandler.removeMessages(MSG_HIDE_TRANSIENT);
+            mHideTransientMessageHandler.cancel();
             updateTransient();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 0b1807d..2ca0b00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -143,6 +143,9 @@
     /** Sets the state of whether sysui is dozing or not. */
     default void setDozing(boolean dozing) {}
 
+    /** Sets the state of whether sysui is dreaming or not. */
+    default void setDreaming(boolean dreaming) {}
+
     /** Sets the state of whether plugin open is forced or not. */
     default void setForcePluginOpen(boolean forcePluginOpen, Object token) {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 186e6dc..784e2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -129,6 +129,11 @@
     private boolean mIsDozing;
 
     /**
+     * If the device is currently dreaming or not.
+     */
+    private boolean mIsDreaming;
+
+    /**
      * If the status bar is currently expanded or not.
      */
     private boolean mIsExpanded;
@@ -294,6 +299,29 @@
     }
 
     @Override
+    public boolean setIsDreaming(boolean isDreaming) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "setIsDreaming:" + isDreaming);
+        }
+        if (mIsDreaming == isDreaming) {
+            return false;
+        }
+
+        mIsDreaming = isDreaming;
+
+        synchronized (mListeners) {
+            String tag = getClass().getSimpleName() + "#setIsDreaming";
+            DejankUtils.startDetectingBlockingIpcs(tag);
+            for (RankedListener rl : new ArrayList<>(mListeners)) {
+                rl.mListener.onDreamingChanged(isDreaming);
+            }
+            DejankUtils.stopDetectingBlockingIpcs(tag);
+        }
+
+        return true;
+    }
+
+    @Override
     public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
         if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
             if (animated && mDozeAmountTarget == dozeAmount) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index e0cf812..088c568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -99,6 +99,13 @@
     boolean setIsDozing(boolean isDozing);
 
     /**
+     * Update the dreaming state from {@link CentralSurfaces}'s perspective
+     * @param isDreaming whether we are dreaming
+     * @return {@code true} if the state changed, else {@code false}
+     */
+    boolean setIsDreaming(boolean isDreaming);
+
+    /**
      * Changes the current doze amount, also starts the
      * {@link com.android.internal.jank.InteractionJankMonitor InteractionJankMonitor} as possible.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java
index f960eb7..ed32008 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkController.java
@@ -47,8 +47,6 @@
     void addEmergencyListener(EmergencyListener listener);
     /** */
     void removeEmergencyListener(EmergencyListener listener);
-    /** */
-    boolean hasEmergencyCryptKeeperText();
 
     /** */
     boolean isRadioOn();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 5f5418f..6e74542 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -84,7 +84,6 @@
 import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-import com.android.systemui.statusbar.policy.EncryptionHelper;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.CarrierConfigTracker;
 
@@ -1486,11 +1485,6 @@
     }
 
     /** */
-    public boolean hasEmergencyCryptKeeperText() {
-        return EncryptionHelper.IS_DATA_ENCRYPTED;
-    }
-
-    /** */
     public boolean isRadioOn() {
         return !mAirplaneMode;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 9a65e34..d7568a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -25,17 +25,21 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.AnimationFeatureFlags;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.carrier.QSCarrierGroupController;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.MediaArtworkProcessor;
@@ -181,11 +185,12 @@
     @SysUISingleton
     static CommandQueue provideCommandQueue(
             Context context,
+            DisplayTracker displayTracker,
             ProtoTracer protoTracer,
             CommandRegistry registry,
             DumpHandler dumpHandler
     ) {
-        return new CommandQueue(context, protoTracer, registry, dumpHandler);
+        return new CommandQueue(context, displayTracker, protoTracer, registry, dumpHandler);
     }
 
     /**
@@ -281,7 +286,8 @@
     static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager,
             KeyguardStateController keyguardStateController,
             Lazy<AlternateBouncerInteractor> alternateBouncerInteractor,
-            InteractionJankMonitor interactionJankMonitor) {
+            InteractionJankMonitor interactionJankMonitor,
+            AnimationFeatureFlags animationFeatureFlags) {
         DialogLaunchAnimator.Callback callback = new DialogLaunchAnimator.Callback() {
             @Override
             public boolean isDreaming() {
@@ -303,6 +309,19 @@
                 return alternateBouncerInteractor.get().canShowAlternateBouncerForFingerprint();
             }
         };
-        return new DialogLaunchAnimator(callback, interactionJankMonitor);
+        return new DialogLaunchAnimator(callback, interactionJankMonitor, animationFeatureFlags);
+    }
+
+    /**
+     */
+    @Provides
+    @SysUISingleton
+    static AnimationFeatureFlags provideAnimationFeatureFlags(FeatureFlags featureFlags) {
+        return new AnimationFeatureFlags() {
+            @Override
+            public boolean isPredictiveBackQsDialogAnim() {
+                return featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM);
+            }
+        };
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
index 1e7fc93..197cf56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt
@@ -54,7 +54,7 @@
  * their respective views based on the progress of the animator. Interpolation differences TBD
  */
 @SysUISingleton
-class SystemStatusAnimationScheduler @Inject constructor(
+open class SystemStatusAnimationScheduler @Inject constructor(
     private val coordinator: SystemEventCoordinator,
     private val chipAnimationController: SystemEventChipAnimationController,
     private val statusBarWindowController: StatusBarWindowController,
@@ -66,7 +66,7 @@
     companion object {
         private const val PROPERTY_ENABLE_IMMERSIVE_INDICATOR = "enable_immersive_indicator"
     }
-    private fun isImmersiveIndicatorEnabled(): Boolean {
+    public fun isImmersiveIndicatorEnabled(): Boolean {
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
                 PROPERTY_ENABLE_IMMERSIVE_INDICATOR, true)
     }
@@ -76,18 +76,22 @@
 
     /** True if the persistent privacy dot should be active */
     var hasPersistentDot = false
-        private set
+        protected set
 
     private var scheduledEvent: StatusEvent? = null
     private var cancelExecutionRunnable: Runnable? = null
     private val listeners = mutableSetOf<SystemStatusAnimationCallback>()
 
+    fun getListeners(): MutableSet<SystemStatusAnimationCallback> {
+        return listeners
+    }
+
     init {
         coordinator.attachScheduler(this)
         dumpManager.registerDumpable(TAG, this)
     }
 
-    fun onStatusEvent(event: StatusEvent) {
+    open fun onStatusEvent(event: StatusEvent) {
         // Ignore any updates until the system is up and running
         if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
             return
@@ -139,7 +143,7 @@
         }
     }
 
-    private fun isTooEarly(): Boolean {
+    public fun isTooEarly(): Boolean {
         return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
index 3a4731a..92a8356 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
@@ -20,9 +20,9 @@
 import android.annotation.CallSuper
 import android.os.Looper
 import android.view.Choreographer
-import android.view.Display
 import android.view.InputEvent
 import android.view.MotionEvent
+import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.shared.system.InputChannelCompat
 import com.android.systemui.shared.system.InputMonitorCompat
 
@@ -38,7 +38,8 @@
  * gesture is detected, they should call [onGestureDetected] (which will notify the callbacks).
  */
 abstract class GenericGestureDetector(
-    private val tag: String
+    private val tag: String,
+    private val displayTracker: DisplayTracker
 ) {
     /**
      * Active callbacks, each associated with a tag. Gestures will only be monitored if
@@ -86,7 +87,7 @@
     internal open fun startGestureListening() {
         stopGestureListening()
 
-        inputMonitor = InputMonitorCompat(tag, Display.DEFAULT_DISPLAY).also {
+        inputMonitor = InputMonitorCompat(tag, displayTracker.defaultDisplayId).also {
             inputReceiver = it.getInputReceiver(
                 Looper.getMainLooper(),
                 Choreographer.getInstance(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
index 6115819..693ae66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
@@ -17,90 +17,27 @@
 package com.android.systemui.statusbar.gesture
 
 import android.content.Context
-import android.view.InputEvent
 import android.view.MotionEvent
-import android.view.MotionEvent.ACTION_CANCEL
-import android.view.MotionEvent.ACTION_DOWN
-import android.view.MotionEvent.ACTION_MOVE
-import android.view.MotionEvent.ACTION_UP
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import javax.inject.Inject
 
-/**
- * A class to detect when a user swipes away the status bar. To be notified when the swipe away
- * gesture is detected, add a callback via [addOnGestureDetectedCallback].
- */
+/** A class to detect when a user swipes away the status bar. */
 @SysUISingleton
-open class SwipeStatusBarAwayGestureHandler @Inject constructor(
+class SwipeStatusBarAwayGestureHandler
+@Inject
+constructor(
     context: Context,
+    displayTracker: DisplayTracker,
+    logger: SwipeUpGestureLogger,
     private val statusBarWindowController: StatusBarWindowController,
-    private val logger: SwipeStatusBarAwayGestureLogger
-) : GenericGestureDetector(SwipeStatusBarAwayGestureHandler::class.simpleName!!) {
-
-    private var startY: Float = 0f
-    private var startTime: Long = 0L
-    private var monitoringCurrentTouch: Boolean = false
-
-    private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
-        com.android.internal.R.dimen.system_gestures_start_threshold
-    )
-
-    override fun onInputEvent(ev: InputEvent) {
-        if (ev !is MotionEvent) {
-            return
-        }
-
-        when (ev.actionMasked) {
-            ACTION_DOWN -> {
-                if (
-                    // Gesture starts just below the status bar
-                    ev.y >= statusBarWindowController.statusBarHeight
-                    && ev.y <= 3 * statusBarWindowController.statusBarHeight
-                ) {
-                    logger.logGestureDetectionStarted(ev.y.toInt())
-                    startY = ev.y
-                    startTime = ev.eventTime
-                    monitoringCurrentTouch = true
-                } else {
-                    monitoringCurrentTouch = false
-                }
-            }
-            ACTION_MOVE -> {
-                if (!monitoringCurrentTouch) {
-                    return
-                }
-                if (
-                    // Gesture is up
-                    ev.y < startY
-                    // Gesture went far enough
-                    && (startY - ev.y) >= swipeDistanceThreshold
-                    // Gesture completed quickly enough
-                    && (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
-                ) {
-                    monitoringCurrentTouch = false
-                    logger.logGestureDetected(ev.y.toInt())
-                    onGestureDetected(ev)
-                }
-            }
-            ACTION_CANCEL, ACTION_UP -> {
-                if (monitoringCurrentTouch) {
-                    logger.logGestureDetectionEndedWithoutTriggering(ev.y.toInt())
-                }
-                monitoringCurrentTouch = false
-            }
-        }
-    }
-
-    override fun startGestureListening() {
-        super.startGestureListening()
-        logger.logInputListeningStarted()
-    }
-
-    override fun stopGestureListening() {
-        super.stopGestureListening()
-        logger.logInputListeningStopped()
+) : SwipeUpGestureHandler(context, displayTracker, logger, loggerTag = LOGGER_TAG) {
+    override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean {
+        // Gesture starts just below the status bar
+        return ev.y >= statusBarWindowController.statusBarHeight &&
+            ev.y <= 3 * statusBarWindowController.statusBarHeight
     }
 }
 
-private const val SWIPE_TIMEOUT_MS: Long = 500
+private const val LOGGER_TAG = "SwipeStatusBarAway"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
new file mode 100644
index 0000000..452762d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 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.systemui.statusbar.gesture
+
+import android.content.Context
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_CANCEL
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.DisplayTracker
+
+/**
+ * A class to detect a generic "swipe up" gesture. To be notified when the swipe up gesture is
+ * detected, add a callback via [addOnGestureDetectedCallback].
+ */
+@SysUISingleton
+abstract class SwipeUpGestureHandler(
+    context: Context,
+    displayTracker: DisplayTracker,
+    private val logger: SwipeUpGestureLogger,
+    private val loggerTag: String
+) : GenericGestureDetector(SwipeUpGestureHandler::class.simpleName!!, displayTracker) {
+
+    private var startY: Float = 0f
+    private var startTime: Long = 0L
+    private var monitoringCurrentTouch: Boolean = false
+
+    private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
+        com.android.internal.R.dimen.system_gestures_start_threshold
+    )
+
+    override fun onInputEvent(ev: InputEvent) {
+        if (ev !is MotionEvent) {
+            return
+        }
+
+        when (ev.actionMasked) {
+            ACTION_DOWN -> {
+                if (
+                    startOfGestureIsWithinBounds(ev)
+                ) {
+                    logger.logGestureDetectionStarted(loggerTag, ev.y.toInt())
+                    startY = ev.y
+                    startTime = ev.eventTime
+                    monitoringCurrentTouch = true
+                } else {
+                    monitoringCurrentTouch = false
+                }
+            }
+            ACTION_MOVE -> {
+                if (!monitoringCurrentTouch) {
+                    return
+                }
+                if (
+                    // Gesture is up
+                    ev.y < startY &&
+                    // Gesture went far enough
+                    (startY - ev.y) >= swipeDistanceThreshold &&
+                    // Gesture completed quickly enough
+                    (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
+                ) {
+                    monitoringCurrentTouch = false
+                    logger.logGestureDetected(loggerTag, ev.y.toInt())
+                    onGestureDetected(ev)
+                }
+            }
+            ACTION_CANCEL, ACTION_UP -> {
+                if (monitoringCurrentTouch) {
+                    logger.logGestureDetectionEndedWithoutTriggering(loggerTag, ev.y.toInt())
+                }
+                monitoringCurrentTouch = false
+            }
+        }
+    }
+
+    /**
+     * Returns true if the [ACTION_DOWN] event falls within bounds for this specific swipe-up
+     * gesture.
+     *
+     * Implementations must override this method to specify what part(s) of the screen are valid
+     * locations for the swipe up gesture to start at.
+     */
+    abstract fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean
+
+    override fun startGestureListening() {
+        super.startGestureListening()
+        logger.logInputListeningStarted(loggerTag)
+    }
+
+    override fun stopGestureListening() {
+        super.stopGestureListening()
+        logger.logInputListeningStopped(loggerTag)
+    }
+}
+
+private const val SWIPE_TIMEOUT_MS: Long = 500
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
index 9bdff92..9ce6b02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt
@@ -16,49 +16,49 @@
 
 package com.android.systemui.statusbar.gesture
 
-import com.android.systemui.log.dagger.SwipeStatusBarAwayLog
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.SwipeUpLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
 import javax.inject.Inject
 
-/** Log messages for [SwipeStatusBarAwayGestureHandler]. */
-class SwipeStatusBarAwayGestureLogger @Inject constructor(
-    @SwipeStatusBarAwayLog private val buffer: LogBuffer
+/** Log messages for [SwipeUpGestureHandler]. */
+@SysUISingleton
+class SwipeUpGestureLogger @Inject constructor(
+    @SwipeUpLog private val buffer: LogBuffer,
 ) {
-    fun logGestureDetectionStarted(y: Int) {
+    fun logGestureDetectionStarted(tag: String, y: Int) {
         buffer.log(
-            TAG,
+            tag,
             LogLevel.DEBUG,
             { int1 = y },
             { "Beginning gesture detection. y=$int1" }
         )
     }
 
-    fun logGestureDetectionEndedWithoutTriggering(y: Int) {
+    fun logGestureDetectionEndedWithoutTriggering(tag: String, y: Int) {
         buffer.log(
-            TAG,
+            tag,
             LogLevel.DEBUG,
             { int1 = y },
             { "Gesture finished; no swipe up gesture detected. Final y=$int1" }
         )
     }
 
-    fun logGestureDetected(y: Int) {
+    fun logGestureDetected(tag: String, y: Int) {
         buffer.log(
-            TAG,
+            tag,
             LogLevel.INFO,
             { int1 = y },
             { "Gesture detected; notifying callbacks. y=$int1" }
         )
     }
 
-    fun logInputListeningStarted() {
-        buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening started "})
+    fun logInputListeningStarted(tag: String) {
+        buffer.log(tag, LogLevel.VERBOSE, {}, { "Input listening started "})
     }
 
-    fun logInputListeningStopped() {
-        buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening stopped "})
+    fun logInputListeningStopped(tag: String) {
+        buffer.log(tag, LogLevel.VERBOSE, {}, { "Input listening stopped "})
     }
 }
-
-private const val TAG = "SwipeStatusBarAwayGestureHandler"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
index 7ffb07a..a901d597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
@@ -21,6 +21,7 @@
 import android.view.InputEvent
 import android.view.MotionEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.DisplayTracker
 import javax.inject.Inject
 
 /**
@@ -29,8 +30,9 @@
  */
 @SysUISingleton
 class TapGestureDetector @Inject constructor(
-    private val context: Context
-) : GenericGestureDetector(TapGestureDetector::class.simpleName!!) {
+    private val context: Context,
+    displayTracker: DisplayTracker
+) : GenericGestureDetector(TapGestureDetector::class.simpleName!!, displayTracker) {
 
     private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
         override fun onSingleTapUp(e: MotionEvent): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index d6ad7d0..5440fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -35,6 +35,7 @@
 import android.view.ContextThemeWrapper
 import android.view.View
 import android.view.ViewGroup
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.settingslib.Utils
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
@@ -43,6 +44,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.BcSmartspaceConfigPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
@@ -51,20 +53,25 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.regionsampling.RegionSampler
 import com.android.systemui.shared.regionsampling.UpdateColorCallback
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
+import com.android.systemui.statusbar.Weather
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.util.settings.SecureSettings
+import java.time.Instant
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import javax.inject.Named
 
-/**
- * Controller for managing the smartspace view on the lockscreen
- */
+/** Controller for managing the smartspace view on the lockscreen */
 @SysUISingleton
-class LockscreenSmartspaceController @Inject constructor(
+class LockscreenSmartspaceController
+@Inject
+constructor(
         private val context: Context,
         private val featureFlags: FeatureFlags,
         private val smartspaceManager: SmartspaceManager,
@@ -77,18 +84,27 @@
         private val statusBarStateController: StatusBarStateController,
         private val deviceProvisionedController: DeviceProvisionedController,
         private val bypassController: KeyguardBypassController,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
         private val execution: Execution,
         @Main private val uiExecutor: Executor,
         @Background private val bgExecutor: Executor,
         @Main private val handler: Handler,
-        optionalPlugin: Optional<BcSmartspaceDataPlugin>
+        @Named(DATE_SMARTSPACE_DATA_PLUGIN)
+        optionalDatePlugin: Optional<BcSmartspaceDataPlugin>,
+        @Named(WEATHER_SMARTSPACE_DATA_PLUGIN)
+        optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
+        optionalPlugin: Optional<BcSmartspaceDataPlugin>,
+        optionalConfigPlugin: Optional<BcSmartspaceConfigPlugin>,
 ) {
     companion object {
         private const val TAG = "LockscreenSmartspaceController"
     }
 
     private var session: SmartspaceSession? = null
+    private val datePlugin: BcSmartspaceDataPlugin? = optionalDatePlugin.orElse(null)
+    private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
+    private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
 
     // Smartspace can be used on multiple displays, such as when the user casts their screen
     private var smartspaceViews = mutableSetOf<SmartspaceView>()
@@ -97,7 +113,7 @@
 
     private val regionSamplingEnabled =
             featureFlags.isEnabled(Flags.REGION_SAMPLING)
-
+    private var isContentUpdatedOnce = false
     private var showNotifications = false
     private var showSensitiveContentForCurrentUser = false
     private var showSensitiveContentForManagedUser = false
@@ -111,19 +127,6 @@
         override fun onViewAttachedToWindow(v: View) {
             smartspaceViews.add(v as SmartspaceView)
 
-            if (regionSamplingEnabled) {
-                var regionSampler = RegionSampler(
-                        v,
-                        uiExecutor,
-                        bgExecutor,
-                        regionSamplingEnabled,
-                        updateFun
-                )
-                initializeTextColors(regionSampler)
-                regionSampler.startRegionSampler()
-                regionSamplers.put(v, regionSampler)
-            }
-
             connectSession()
 
             updateTextColorFromWallpaper()
@@ -133,12 +136,6 @@
         override fun onViewDetachedFromWindow(v: View) {
             smartspaceViews.remove(v as SmartspaceView)
 
-            if (regionSamplingEnabled) {
-                var regionSampler = regionSamplers.getValue(v)
-                regionSampler.stopRegionSampler()
-                regionSamplers.remove(v)
-            }
-
             if (smartspaceViews.isEmpty()) {
                 disconnect()
             }
@@ -147,8 +144,41 @@
 
     private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
         execution.assertIsMainThread()
+
+        // The weather data plugin takes unfiltered targets and performs the filtering internally.
+        weatherPlugin?.onTargetsAvailable(targets)
+
         val filteredTargets = targets.filter(::filterSmartspaceTarget)
         plugin?.onTargetsAvailable(filteredTargets)
+        if (!isContentUpdatedOnce) {
+            for (v in smartspaceViews) {
+                if (regionSamplingEnabled) {
+                    var regionSampler = RegionSampler(
+                        v as View,
+                        uiExecutor,
+                        bgExecutor,
+                        regionSamplingEnabled,
+                        updateFun
+                    )
+                    initializeTextColors(regionSampler)
+                    regionSamplers[v] = regionSampler
+                    regionSampler.startRegionSampler()
+                }
+                updateTextColorFromWallpaper()
+            }
+            isContentUpdatedOnce = true
+        }
+
+        val now = Instant.now()
+        val weatherTarget = targets.find { t ->
+            t.featureType == SmartspaceTarget.FEATURE_WEATHER &&
+                    now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) &&
+                    now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis))
+        }
+        if (weatherTarget != null) {
+            val weatherData = Weather.fromBundle(weatherTarget.baseAction.extras)
+            keyguardUpdateMonitor.sendWeatherData(weatherData)
+        }
     }
 
     private val userTrackerCallback = object : UserTracker.Callback {
@@ -204,7 +234,14 @@
     fun isEnabled(): Boolean {
         execution.assertIsMainThread()
 
-        return featureFlags.isEnabled(Flags.SMARTSPACE) && plugin != null
+        return plugin != null
+    }
+
+    fun isDateWeatherDecoupled(): Boolean {
+        execution.assertIsMainThread()
+
+        return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) &&
+                datePlugin != null && weatherPlugin != null
     }
 
     private fun updateBypassEnabled() {
@@ -213,6 +250,44 @@
     }
 
     /**
+     * Constructs the date view and connects it to the smartspace service.
+     */
+    fun buildAndConnectDateView(parent: ViewGroup): View? {
+        execution.assertIsMainThread()
+
+        if (!isEnabled()) {
+            throw RuntimeException("Cannot build view when not enabled")
+        }
+        if (!isDateWeatherDecoupled()) {
+            throw RuntimeException("Cannot build date view when not decoupled")
+        }
+
+        val view = buildView(parent, datePlugin)
+        connectSession()
+
+        return view
+    }
+
+    /**
+     * Constructs the weather view and connects it to the smartspace service.
+     */
+    fun buildAndConnectWeatherView(parent: ViewGroup): View? {
+        execution.assertIsMainThread()
+
+        if (!isEnabled()) {
+            throw RuntimeException("Cannot build view when not enabled")
+        }
+        if (!isDateWeatherDecoupled()) {
+            throw RuntimeException("Cannot build weather view when not decoupled")
+        }
+
+        val view = buildView(parent, weatherPlugin)
+        connectSession()
+
+        return view
+    }
+
+    /**
      * Constructs the smartspace view and connects it to the smartspace service.
      */
     fun buildAndConnectView(parent: ViewGroup): View? {
@@ -222,17 +297,17 @@
             throw RuntimeException("Cannot build view when not enabled")
         }
 
-        val view = buildView(parent)
+        val view = buildView(parent, plugin, configPlugin)
         connectSession()
 
         return view
     }
 
-    fun requestSmartspaceUpdate() {
-        session?.requestSmartspaceUpdate()
-    }
-
-    private fun buildView(parent: ViewGroup): View? {
+    private fun buildView(
+            parent: ViewGroup,
+            plugin: BcSmartspaceDataPlugin?,
+            configPlugin: BcSmartspaceConfigPlugin? = null
+    ): View? {
         if (plugin == null) {
             return null
         }
@@ -240,6 +315,7 @@
         val ssView = plugin.getView(parent)
         ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
         ssView.registerDataProvider(plugin)
+        configPlugin?.let { ssView.registerConfigProvider(it) }
 
         ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
             override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
@@ -270,7 +346,8 @@
     }
 
     private fun connectSession() {
-        if (plugin == null || session != null || smartspaceViews.isEmpty()) {
+        if (datePlugin == null && weatherPlugin == null && plugin == null) return
+        if (session != null || smartspaceViews.isEmpty()) {
             return
         }
 
@@ -307,15 +384,22 @@
         statusBarStateController.addCallback(statusBarStateListener)
         bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
 
-        plugin.registerSmartspaceEventNotifier {
-                e -> session?.notifySmartspaceEvent(e)
-        }
+        datePlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
+        weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
+        plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
 
         updateBypassEnabled()
         reloadSmartspace()
     }
 
     /**
+     * Requests the smartspace session for an update.
+     */
+    fun requestSmartspaceUpdate() {
+        session?.requestSmartspaceUpdate()
+    }
+
+    /**
      * Disconnects the smartspace view from the smartspace service and cleans up any resources.
      */
     fun disconnect() {
@@ -338,9 +422,15 @@
         bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
         session = null
 
+        datePlugin?.registerSmartspaceEventNotifier(null)
+
+        weatherPlugin?.registerSmartspaceEventNotifier(null)
+        weatherPlugin?.onTargetsAvailable(emptyList())
+
         plugin?.registerSmartspaceEventNotifier(null)
         plugin?.onTargetsAvailable(emptyList())
-        Log.d(TAG, "Ending smartspace session for lockscreen")
+
+        Log.d(TAG, "Ended smartspace session for lockscreen")
     }
 
     fun addListener(listener: SmartspaceTargetListener) {
@@ -354,8 +444,11 @@
     }
 
     private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
+        if (isDateWeatherDecoupled()) {
+            return t.featureType != SmartspaceTarget.FEATURE_WEATHER
+        }
         if (!showNotifications) {
-            return t.getFeatureType() == SmartspaceTarget.FEATURE_WEATHER
+            return t.featureType == SmartspaceTarget.FEATURE_WEATHER
         }
         return when (t.userHandle) {
             userTracker.userHandle -> {
@@ -394,7 +487,8 @@
 
     private fun updateTextColorFromWallpaper() {
         val wallpaperManager = WallpaperManager.getInstance(context)
-        if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists()) {
+        if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists() ||
+            regionSamplers.isEmpty()) {
             val wallpaperTextColor =
                     Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
             smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 3072c81..4856759 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -25,23 +25,19 @@
     val context: Context,
     val featureFlags: FeatureFlags
 ) {
+    init {
+        featureFlags.addListener(Flags.DISABLE_FSI) { event -> event.requestNoRestart() }
+    }
+
     fun isDevLoggingEnabled(): Boolean =
         featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
 
-    fun isSmartspaceDedupingEnabled(): Boolean = featureFlags.isEnabled(Flags.SMARTSPACE)
-
     fun fullScreenIntentRequiresKeyguard(): Boolean =
         featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
 
     fun fsiOnDNDUpdate(): Boolean = featureFlags.isEnabled(Flags.FSI_ON_DND_UPDATE)
 
-    val isStabilityIndexFixEnabled: Boolean by lazy {
-        featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX)
-    }
-
-    val isSemiStableSortEnabled: Boolean by lazy {
-        featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
-    }
+    fun disableFsi(): Boolean = featureFlags.isEnabled(Flags.DISABLE_FSI)
 
     val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
         featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index aeae89c..7e53d54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.KeyguardBypassController.OnBypassStateChangedListener
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
@@ -38,7 +39,6 @@
 import javax.inject.Inject
 import kotlin.math.min
 
-
 @SysUISingleton
 class NotificationWakeUpCoordinator @Inject constructor(
     dumpManager: DumpManager,
@@ -68,6 +68,7 @@
     private var mLinearDozeAmount: Float = 0.0f
     private var mDozeAmount: Float = 0.0f
     private var mDozeAmountSource: String = "init"
+    private var mNotifsHiddenByDozeAmountOverride: Boolean = false
     private var mNotificationVisibleAmount = 0.0f
     private var mNotificationsVisible = false
     private var mNotificationsVisibleForExpansion = false
@@ -130,6 +131,7 @@
                 }
             }
         }
+
     /**
      * True if we can show pulsing heads up notifications
      */
@@ -149,10 +151,19 @@
             return canShow
         }
 
+    private val bypassStateChangedListener = object : OnBypassStateChangedListener {
+        override fun onBypassStateChanged(isEnabled: Boolean) {
+            // When the bypass state changes, we have to check whether we should re-show the
+            // notifications by clearing the doze amount override which hides them.
+            maybeClearDozeAmountOverrideHidingNotifs()
+        }
+    }
+
     init {
         dumpManager.registerDumpable(this)
         mHeadsUpManager.addListener(this)
         statusBarStateController.addCallback(this)
+        bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
         addListener(object : WakeUpListener {
             override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
                 if (isFullyHidden && mNotificationsVisibleForExpansion) {
@@ -261,12 +272,18 @@
         setDozeAmount(linear, eased, source = "StatusBar")
     }
 
-    fun setDozeAmount(linear: Float, eased: Float, source: String) {
+    fun setDozeAmount(
+        linear: Float,
+        eased: Float,
+        source: String,
+        hidesNotifsByOverride: Boolean = false
+    ) {
         val changed = linear != mLinearDozeAmount
         logger.logSetDozeAmount(linear, eased, source, statusBarStateController.state, changed)
         mLinearDozeAmount = linear
         mDozeAmount = eased
         mDozeAmountSource = source
+        mNotifsHiddenByDozeAmountOverride = hidesNotifsByOverride
         mStackScrollerController.setDozeAmount(mDozeAmount)
         updateHideAmount()
         if (changed && linear == 0.0f) {
@@ -295,6 +312,8 @@
             return
         }
 
+        maybeClearDozeAmountOverrideHidingNotifs()
+
         if (bypassController.bypassEnabled &&
                 newState == StatusBarState.KEYGUARD && state == StatusBarState.SHADE_LOCKED &&
             (!statusBarStateController.isDozing || shouldAnimateVisibility())) {
@@ -325,7 +344,8 @@
     private fun overrideDozeAmountIfBypass(): Boolean {
         if (bypassController.bypassEnabled) {
             if (statusBarStateController.state == StatusBarState.KEYGUARD) {
-                setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
+                setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)",
+                        hidesNotifsByOverride = true)
             } else {
                 setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
             }
@@ -335,6 +355,37 @@
     }
 
     /**
+     * If the last [setDozeAmount] call was an override to hide notifications, then this call will
+     * check for the set of states that may have caused that override, and if none of them still
+     * apply, and the device is awake or not on the keyguard, then dozeAmount will be reset to 0.
+     * This fixes bugs where the bypass state changing could result in stale overrides, hiding
+     * notifications either on the inside screen or even after unlock.
+     */
+    private fun maybeClearDozeAmountOverrideHidingNotifs() {
+        if (mNotifsHiddenByDozeAmountOverride) {
+            val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
+            val dozing = statusBarStateController.isDozing
+            val bypass = bypassController.bypassEnabled
+            val animating =
+                    screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()
+            // Overrides are set by [overrideDozeAmountIfAnimatingScreenOff] and
+            // [overrideDozeAmountIfBypass] based on 'animating' and 'bypass' respectively, so only
+            // clear the override if both those conditions are cleared.  But also require either
+            // !dozing or !onKeyguard because those conditions should indicate that we intend
+            // notifications to be visible, and thus it is safe to unhide them.
+            val willRemove = (!onKeyguard || !dozing) && !bypass && !animating
+            logger.logMaybeClearDozeAmountOverrideHidingNotifs(
+                    willRemove = willRemove,
+                    onKeyguard = onKeyguard, dozing = dozing,
+                    bypass = bypass, animating = animating,
+            )
+            if (willRemove) {
+                setDozeAmount(0f, 0f, source = "Removed: $mDozeAmountSource")
+            }
+        }
+    }
+
+    /**
      * If we're playing the screen off animation, force the notification doze amount to be 1f (fully
      * dozing). This is needed so that the notifications aren't briefly visible as the screen turns
      * off and dozeAmount goes from 1f to 0f.
@@ -344,7 +395,8 @@
      */
     private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
         if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
-            setDozeAmount(1f, 1f, source = "Override: animating screen off")
+            setDozeAmount(1f, 1f, source = "Override: animating screen off",
+                    hidesNotifsByOverride = true)
             return true
         }
 
@@ -430,6 +482,7 @@
         pw.println("mLinearDozeAmount: $mLinearDozeAmount")
         pw.println("mDozeAmount: $mDozeAmount")
         pw.println("mDozeAmountSource: $mDozeAmountSource")
+        pw.println("mNotifsHiddenByDozeAmountOverride: $mNotifsHiddenByDozeAmountOverride")
         pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
         pw.println("mNotificationsVisible: $mNotificationsVisible")
         pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
index de18b0c..4464531 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -46,6 +46,25 @@
         )
     }
 
+    fun logMaybeClearDozeAmountOverrideHidingNotifs(
+        willRemove: Boolean,
+        onKeyguard: Boolean,
+        dozing: Boolean,
+        bypass: Boolean,
+        animating: Boolean,
+    ) {
+        buffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 =
+                    "willRemove=$willRemove onKeyguard=$onKeyguard dozing=$dozing" +
+                        " bypass=$bypass animating=$animating"
+            },
+            { "maybeClearDozeAmountOverrideHidingNotifs() $str1" }
+        )
+    }
+
     fun logOnDozeAmountChanged(linear: Float, eased: Float) {
         buffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 84ab0d1..b5fce41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -98,13 +98,11 @@
      * This can happen if the entry is removed from a group that was broken up or if the entry was
      * filtered out during any of the filtering steps.
      */
-    fun detach(includingStableIndex: Boolean) {
+    fun detach() {
         parent = null
         section = null
         promoter = null
-        if (includingStableIndex) {
-            stableIndex = -1
-        }
+        stableIndex = -1
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 65a21a4..4065b98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -965,8 +965,7 @@
      * filtered out during any of the filtering steps.
      */
     private void annulAddition(ListEntry entry) {
-        // NOTE(b/241229236): Don't clear stableIndex until we fix stability fragility
-        entry.getAttachState().detach(/* includingStableIndex= */ mFlags.isSemiStableSortEnabled());
+        entry.getAttachState().detach();
     }
 
     private void assignSections() {
@@ -986,50 +985,10 @@
 
     private void sortListAndGroups() {
         Trace.beginSection("ShadeListBuilder.sortListAndGroups");
-        if (mFlags.isSemiStableSortEnabled()) {
-            sortWithSemiStableSort();
-        } else {
-            sortWithLegacyStability();
-        }
+        sortWithSemiStableSort();
         Trace.endSection();
     }
 
-    private void sortWithLegacyStability() {
-        // Sort all groups and the top level list
-        for (ListEntry entry : mNotifList) {
-            if (entry instanceof GroupEntry) {
-                GroupEntry parent = (GroupEntry) entry;
-                parent.sortChildren(mGroupChildrenComparator);
-            }
-        }
-        mNotifList.sort(mTopLevelComparator);
-        assignIndexes(mNotifList);
-
-        // Check for suppressed order changes
-        if (!getStabilityManager().isEveryChangeAllowed()) {
-            mForceReorderable = true;
-            boolean isSorted = isShadeSortedLegacy();
-            mForceReorderable = false;
-            if (!isSorted) {
-                getStabilityManager().onEntryReorderSuppressed();
-            }
-        }
-    }
-
-    private boolean isShadeSortedLegacy() {
-        if (!isSorted(mNotifList, mTopLevelComparator)) {
-            return false;
-        }
-        for (ListEntry entry : mNotifList) {
-            if (entry instanceof GroupEntry) {
-                if (!isSorted(((GroupEntry) entry).getChildren(), mGroupChildrenComparator)) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
     private void sortWithSemiStableSort() {
         // Sort each group's children
         boolean allSorted = true;
@@ -1100,29 +1059,16 @@
                 sectionMemberIndex = 0;
                 currentSection = section;
             }
-            if (mFlags.isStabilityIndexFixEnabled()) {
-                entry.getAttachState().setStableIndex(sectionMemberIndex++);
-                if (entry instanceof GroupEntry) {
-                    final GroupEntry parent = (GroupEntry) entry;
-                    final NotificationEntry summary = parent.getSummary();
-                    if (summary != null) {
-                        summary.getAttachState().setStableIndex(sectionMemberIndex++);
-                    }
-                    for (NotificationEntry child : parent.getChildren()) {
-                        child.getAttachState().setStableIndex(sectionMemberIndex++);
-                    }
+            entry.getAttachState().setStableIndex(sectionMemberIndex++);
+            if (entry instanceof GroupEntry) {
+                final GroupEntry parent = (GroupEntry) entry;
+                final NotificationEntry summary = parent.getSummary();
+                if (summary != null) {
+                    summary.getAttachState().setStableIndex(sectionMemberIndex++);
                 }
-            } else {
-                // This old implementation uses the same index number for the group as the first
-                // child, and fails to assign an index to the summary.  Remove once tested.
-                entry.getAttachState().setStableIndex(sectionMemberIndex);
-                if (entry instanceof GroupEntry) {
-                    final GroupEntry parent = (GroupEntry) entry;
-                    for (NotificationEntry child : parent.getChildren()) {
-                        child.getAttachState().setStableIndex(sectionMemberIndex++);
-                    }
+                for (NotificationEntry child : parent.getChildren()) {
+                    child.getAttachState().setStableIndex(sectionMemberIndex++);
                 }
-                sectionMemberIndex++;
             }
         }
     }
@@ -1272,11 +1218,6 @@
                 o2.getSectionIndex());
         if (cmp != 0) return cmp;
 
-        cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
-                getStableOrderIndex(o1),
-                getStableOrderIndex(o2));
-        if (cmp != 0) return cmp;
-
         NotifComparator sectionComparator = getSectionComparator(o1, o2);
         if (sectionComparator != null) {
             cmp = sectionComparator.compare(o1, o2);
@@ -1301,12 +1242,7 @@
 
 
     private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
-        int cmp = mFlags.isSemiStableSortEnabled() ? 0 : Integer.compare(
-                getStableOrderIndex(o1),
-                getStableOrderIndex(o2));
-        if (cmp != 0) return cmp;
-
-        cmp = Integer.compare(
+        int cmp = Integer.compare(
                 o1.getRepresentativeEntry().getRanking().getRank(),
                 o2.getRepresentativeEntry().getRanking().getRank());
         if (cmp != 0) return cmp;
@@ -1317,25 +1253,6 @@
         return cmp;
     };
 
-    /**
-     * A flag that is set to true when we want to run the comparators as if all reordering is
-     * allowed.  This is used to check if the list is "out of order" after the sort is complete.
-     */
-    private boolean mForceReorderable = false;
-
-    private int getStableOrderIndex(ListEntry entry) {
-        if (mForceReorderable) {
-            // this is used to determine if the list is correctly sorted
-            return -1;
-        }
-        if (getStabilityManager().isEntryReorderingAllowed(entry)) {
-            // let the stability manager constrain or allow reordering
-            return -1;
-        }
-        // NOTE(b/241229236): Can't use cleared section index until we fix stability fragility
-        return entry.getPreviousAttachState().getStableIndex();
-    }
-
     @Nullable
     private Integer getStableOrderRank(ListEntry entry) {
         if (getStabilityManager().isEntryReorderingAllowed(entry)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 76252d0..e996b78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -114,10 +114,10 @@
             .onStart { emit(Unit) }
             // for each change, lookup the new value
             .map {
-                secureSettings.getBoolForUser(
+                secureSettings.getIntForUser(
                     Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
                     UserHandle.USER_CURRENT,
-                )
+                ) == 1
             }
             // perform lookups on the bg thread pool
             .flowOn(bgDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 1399385..03a3ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -88,9 +88,7 @@
         mCoordinators.add(viewConfigCoordinator)
         mCoordinators.add(visualStabilityCoordinator)
         mCoordinators.add(sensitiveContentCoordinator)
-        if (notifPipelineFlags.isSmartspaceDedupingEnabled()) {
-            mCoordinators.add(smartspaceDedupingCoordinator)
-        }
+        mCoordinators.add(smartspaceDedupingCoordinator)
         mCoordinators.add(headsUpCoordinator)
         mCoordinators.add(gutsCoordinator)
         mCoordinators.add(preparationCoordinator)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 808638a..8436ff7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -16,27 +16,15 @@
 
 package com.android.systemui.statusbar.notification.dagger;
 
-import android.app.INotificationManager;
 import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutManager;
-import android.os.Handler;
-import android.view.accessibility.AccessibilityManager;
 
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeEventsModule;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
@@ -50,7 +38,6 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
 import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderModule;
 import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
@@ -72,22 +59,17 @@
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
 import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
-import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.wmshell.BubblesManager;
 
-import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import javax.inject.Provider;
 
 import dagger.Binds;
-import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -109,48 +91,6 @@
     @Binds
     StackScrollAlgorithm.BypassController bindBypassController(KeyguardBypassController impl);
 
-    /** Provides an instance of {@link NotificationGutsManager} */
-    @SysUISingleton
-    @Provides
-    static NotificationGutsManager provideNotificationGutsManager(
-            Context context,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            @Main Handler mainHandler,
-            @Background Handler bgHandler,
-            AccessibilityManager accessibilityManager,
-            HighPriorityProvider highPriorityProvider,
-            INotificationManager notificationManager,
-            PeopleSpaceWidgetManager peopleSpaceWidgetManager,
-            LauncherApps launcherApps,
-            ShortcutManager shortcutManager,
-            ChannelEditorDialogController channelEditorDialogController,
-            UserContextProvider contextTracker,
-            AssistantFeedbackController assistantFeedbackController,
-            Optional<BubblesManager> bubblesManagerOptional,
-            UiEventLogger uiEventLogger,
-            OnUserInteractionCallback onUserInteractionCallback,
-            ShadeController shadeController) {
-        return new NotificationGutsManager(
-                context,
-                centralSurfacesOptionalLazy,
-                mainHandler,
-                bgHandler,
-                accessibilityManager,
-                highPriorityProvider,
-                notificationManager,
-                peopleSpaceWidgetManager,
-                launcherApps,
-                shortcutManager,
-                channelEditorDialogController,
-                contextTracker,
-                assistantFeedbackController,
-                bubblesManagerOptional,
-                uiEventLogger,
-                onUserInteractionCallback,
-                shadeController
-        );
-    }
-
     /** Provides an instance of {@link NotifGutsViewManager} */
     @Binds
     NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index 7136cad..bc881ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -31,6 +31,10 @@
      */
     enum FullScreenIntentDecision {
         /**
+         * Full screen intents are disabled.
+         */
+        NO_FSI_DISABLED(false),
+        /**
          * No full screen intent included, so there is nothing to show.
          */
         NO_FULL_SCREEN_INTENT(false),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index d9dacfd..9bcf92d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -28,7 +28,6 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.dreams.IDreamManager;
 import android.service.notification.StatusBarNotification;
@@ -40,6 +39,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -74,6 +74,7 @@
     private final NotifPipelineFlags mFlags;
     private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
     private final UiEventLogger mUiEventLogger;
+    private final UserTracker mUserTracker;
 
     @VisibleForTesting
     protected boolean mUseHeadsUp = false;
@@ -114,7 +115,8 @@
             @Main Handler mainHandler,
             NotifPipelineFlags flags,
             KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            UserTracker userTracker) {
         mContentResolver = contentResolver;
         mPowerManager = powerManager;
         mDreamManager = dreamManager;
@@ -127,6 +129,7 @@
         mFlags = flags;
         mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
         mUiEventLogger = uiEventLogger;
+        mUserTracker = userTracker;
         ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
             @Override
             public void onChange(boolean selfChange) {
@@ -236,6 +239,9 @@
 
     @Override
     public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) {
+        if (mFlags.disableFsi()) {
+            return FullScreenIntentDecision.NO_FSI_DISABLED;
+        }
         if (entry.getSbn().getNotification().fullScreenIntent == null) {
             return FullScreenIntentDecision.NO_FULL_SCREEN_INTENT;
         }
@@ -325,6 +331,9 @@
         final int uid = entry.getSbn().getUid();
         final String packageName = entry.getSbn().getPackageName();
         switch (decision) {
+            case NO_FSI_DISABLED:
+                mLogger.logNoFullscreen(entry, "Disabled");
+                return;
             case NO_FULL_SCREEN_INTENT:
                 return;
             case NO_FSI_SUPPRESSED_BY_DND:
@@ -450,7 +459,7 @@
      * @return true if the entry should ambient pulse, false otherwise
      */
     private boolean shouldHeadsUpWhenDozing(NotificationEntry entry, boolean log) {
-        if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
+        if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(mUserTracker.getUserId())) {
             if (log) mLogger.logNoPulsingSettingDisabled(entry);
             return false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 58f59be..a37bbbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -62,7 +62,7 @@
  * are not.
  */
 public class NotificationLogger implements StateListener {
-    private static final String TAG = "NotificationLogger";
+    static final String TAG = "NotificationLogger";
     private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
 
     /** The minimum delay in ms between reports of notification visibility. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
index ffd931c..197ae1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
@@ -18,9 +18,12 @@
 package com.android.systemui.statusbar.notification.logging
 
 import android.stats.sysui.NotificationEnums
+import android.util.Log
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.DumpsysTableLogger
+import com.android.systemui.dump.Row
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -33,6 +36,7 @@
 
     fun init() {
         dumpManager.registerNormalDumpable(javaClass.simpleName, this)
+        Log.i("NotificationMemory", "Registered dumpable.")
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -45,27 +49,36 @@
 
     /** Renders a table of notification object usage into passed [PrintWriter]. */
     private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
-        pw.println("Notification Object Usage")
-        pw.println("-----------")
-        pw.println(
-            "Package".padEnd(35) +
-                "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
-        )
-        pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
-        pw.println()
-
-        memoryUse.forEach { use ->
-            pw.println(
-                use.packageName.padEnd(35) +
-                    "\t\t" +
-                    "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
-                    (styleEnumToString(use.objectUsage.style).take(15) ?: "").padEnd(15) +
-                    "\t\t${use.objectUsage.styleIcon}\t" +
-                    "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
-                    "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
-                    use.notificationKey
+        val columns =
+            listOf(
+                "Package",
+                "Small Icon",
+                "Large Icon",
+                "Style",
+                "Style Icon",
+                "Big Picture",
+                "Extender",
+                "Extras",
+                "Custom View",
+                "Key"
             )
-        }
+        val rows: List<Row> =
+            memoryUse.map {
+                listOf(
+                    it.packageName,
+                    toKb(it.objectUsage.smallIcon),
+                    toKb(it.objectUsage.largeIcon),
+                    styleEnumToString(it.objectUsage.style),
+                    toKb(it.objectUsage.styleIcon),
+                    toKb(it.objectUsage.bigPicture),
+                    toKb(it.objectUsage.extender),
+                    toKb(it.objectUsage.extras),
+                    it.objectUsage.hasCustomView.toString(),
+                    // | is a  field delimiter in the output format so we need to replace
+                    // it to avoid breakage.
+                    it.notificationKey.replace('|', '│')
+                )
+            }
 
         // Calculate totals for easily glanceable summary.
         data class Totals(
@@ -88,18 +101,23 @@
                 t
             }
 
-        pw.println()
-        pw.println("TOTALS")
-        pw.println(
-            "".padEnd(35) +
-                "\t\t" +
-                "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
-                "".padEnd(15) +
-                "\t\t${toKb(totals.styleIcon)}\t" +
-                "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
-                toKb(totals.extras)
-        )
-        pw.println()
+        val totalsRow: List<Row> =
+            listOf(
+                listOf(
+                    "TOTALS",
+                    toKb(totals.smallIcon),
+                    toKb(totals.largeIcon),
+                    "",
+                    toKb(totals.styleIcon),
+                    toKb(totals.bigPicture),
+                    toKb(totals.extender),
+                    toKb(totals.extras),
+                    "",
+                    ""
+                )
+            )
+        val tableLogger = DumpsysTableLogger("Notification Object Usage", columns, rows + totalsRow)
+        tableLogger.printTableData(pw)
     }
 
     /** Renders a table of notification view usage into passed [PrintWriter] */
@@ -116,40 +134,65 @@
             var softwareBitmapsPenalty: Int = 0,
         )
 
-        val totals = Totals()
-        pw.println("Notification View Usage")
-        pw.println("-----------")
-        pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
-        pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
-        pw.println()
-        memoryUse
-            .filter { it.viewUsage.isNotEmpty() }
-            .forEach { use ->
-                pw.println(use.packageName + " " + use.notificationKey)
-                use.viewUsage.forEach { view ->
-                    pw.println(
-                        "  ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
-                            "\t${view.largeIcon}\t${view.style}" +
-                            "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
-                    )
-
-                    if (view.viewType == ViewType.TOTAL) {
-                        totals.smallIcon += view.smallIcon
-                        totals.largeIcon += view.largeIcon
-                        totals.style += view.style
-                        totals.customViews += view.customViews
-                        totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
+        val columns =
+            listOf(
+                "Package",
+                "View Type",
+                "Small Icon",
+                "Large Icon",
+                "Style Use",
+                "Custom View",
+                "Software Bitmaps",
+                "Key"
+            )
+        val rows =
+            memoryUse
+                .filter { it.viewUsage.isNotEmpty() }
+                .flatMap { use ->
+                    use.viewUsage.map { view ->
+                        listOf(
+                            use.packageName,
+                            view.viewType.toString(),
+                            toKb(view.smallIcon),
+                            toKb(view.largeIcon),
+                            toKb(view.style),
+                            toKb(view.customViews),
+                            toKb(view.softwareBitmapsPenalty),
+                            // | is a  field delimiter in the output format so we need to replace
+                            // it to avoid breakage.
+                            use.notificationKey.replace('|', '│')
+                        )
                     }
                 }
+
+        val totals = Totals()
+        memoryUse
+            .filter { it.viewUsage.isNotEmpty() }
+            .map { it.viewUsage.firstOrNull { view -> view.viewType == ViewType.TOTAL } }
+            .filterNotNull()
+            .forEach { view ->
+                totals.smallIcon += view.smallIcon
+                totals.largeIcon += view.largeIcon
+                totals.style += view.style
+                totals.customViews += view.customViews
+                totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
             }
-        pw.println()
-        pw.println("TOTALS")
-        pw.println(
-            "  ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
-                "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
-                "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
-        )
-        pw.println()
+
+        val totalsRow: List<Row> =
+            listOf(
+                listOf(
+                    "TOTALS",
+                    "",
+                    toKb(totals.smallIcon),
+                    toKb(totals.largeIcon),
+                    toKb(totals.style),
+                    toKb(totals.customViews),
+                    toKb(totals.softwareBitmapsPenalty),
+                    ""
+                )
+            )
+        val tableLogger = DumpsysTableLogger("Notification View Usage", columns, rows + totalsRow)
+        tableLogger.printTableData(pw)
     }
 
     private fun styleEnumToString(styleEnum: Int): String =
@@ -168,6 +211,10 @@
         }
 
     private fun toKb(bytes: Int): String {
-        return (bytes / 1024).toString() + " KB"
+        if (bytes == 0) {
+            return "--"
+        }
+
+        return "%.2f KB".format(bytes / 1024f)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
index ec8501a..cc1103d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.statusbar.notification.logging
 
 import android.app.StatsManager
+import android.util.Log
 import android.util.StatsEvent
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -25,6 +26,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.util.traceSection
+import java.lang.Exception
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlin.math.roundToInt
@@ -82,43 +84,56 @@
                 return StatsManager.PULL_SKIP
             }
 
-            // Notifications can only be retrieved on the main thread, so switch to that thread.
-            val notifications = getAllNotificationsOnMainThread()
-            val notificationMemoryUse =
-                NotificationMemoryMeter.notificationMemoryUse(notifications)
-                    .sortedWith(
-                        compareBy(
-                            { it.packageName },
-                            { it.objectUsage.style },
-                            { it.notificationKey }
+            try {
+                // Notifications can only be retrieved on the main thread, so switch to that thread.
+                val notifications = getAllNotificationsOnMainThread()
+                val notificationMemoryUse =
+                    NotificationMemoryMeter.notificationMemoryUse(notifications)
+                        .sortedWith(
+                            compareBy(
+                                { it.packageName },
+                                { it.objectUsage.style },
+                                { it.notificationKey }
+                            )
+                        )
+                val usageData = aggregateMemoryUsageData(notificationMemoryUse)
+                usageData.forEach { (_, use) ->
+                    data.add(
+                        SysUiStatsLog.buildStatsEvent(
+                            SysUiStatsLog.NOTIFICATION_MEMORY_USE,
+                            use.uid,
+                            use.style,
+                            use.count,
+                            use.countWithInflatedViews,
+                            toKb(use.smallIconObject),
+                            use.smallIconBitmapCount,
+                            toKb(use.largeIconObject),
+                            use.largeIconBitmapCount,
+                            toKb(use.bigPictureObject),
+                            use.bigPictureBitmapCount,
+                            toKb(use.extras),
+                            toKb(use.extenders),
+                            toKb(use.smallIconViews),
+                            toKb(use.largeIconViews),
+                            toKb(use.systemIconViews),
+                            toKb(use.styleViews),
+                            toKb(use.customViews),
+                            toKb(use.softwareBitmaps),
+                            use.seenCount
                         )
                     )
-            val usageData = aggregateMemoryUsageData(notificationMemoryUse)
-            usageData.forEach { (_, use) ->
-                data.add(
-                    SysUiStatsLog.buildStatsEvent(
-                        SysUiStatsLog.NOTIFICATION_MEMORY_USE,
-                        use.uid,
-                        use.style,
-                        use.count,
-                        use.countWithInflatedViews,
-                        toKb(use.smallIconObject),
-                        use.smallIconBitmapCount,
-                        toKb(use.largeIconObject),
-                        use.largeIconBitmapCount,
-                        toKb(use.bigPictureObject),
-                        use.bigPictureBitmapCount,
-                        toKb(use.extras),
-                        toKb(use.extenders),
-                        toKb(use.smallIconViews),
-                        toKb(use.largeIconViews),
-                        toKb(use.systemIconViews),
-                        toKb(use.styleViews),
-                        toKb(use.customViews),
-                        toKb(use.softwareBitmaps),
-                        use.seenCount
-                    )
-                )
+                }
+            } catch (e: InterruptedException) {
+                // This can happen if the device is sleeping or view walking takes too long.
+                // The statsd collector will interrupt the thread and we need to handle it
+                // gracefully.
+                Log.w(NotificationLogger.TAG, "Timed out when measuring notification memory.", e)
+                return@traceSection StatsManager.PULL_SKIP
+            } catch (e: Exception) {
+                // Error while collecting data, this should not crash prod SysUI. Just
+                // log WTF and move on.
+                Log.wtf(NotificationLogger.TAG, "Failed to measure notification memory.", e)
+                return@traceSection StatsManager.PULL_SKIP
             }
 
             return StatsManager.PULL_SUCCESS
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
index 2d04211..6491223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -184,19 +184,21 @@
     private fun computeDrawableUse(drawable: Drawable, seenObjects: HashSet<Int>): Int =
         when (drawable) {
             is BitmapDrawable -> {
-                val ref = System.identityHashCode(drawable.bitmap)
-                if (seenObjects.contains(ref)) {
-                    0
-                } else {
-                    seenObjects.add(ref)
-                    drawable.bitmap.allocationByteCount
-                }
+                drawable.bitmap?.let {
+                    val ref = System.identityHashCode(it)
+                    if (seenObjects.contains(ref)) {
+                        0
+                    } else {
+                        seenObjects.add(ref)
+                        it.allocationByteCount
+                    }
+                } ?: 0
             }
             else -> 0
         }
 
     private fun isDrawableSoftwareBitmap(drawable: Drawable) =
-        drawable is BitmapDrawable && drawable.bitmap.config != Bitmap.Config.HARDWARE
+        drawable is BitmapDrawable && drawable.bitmap?.config != Bitmap.Config.HARDWARE
 
     private fun identifierForView(view: View) =
         if (view.id == View.NO_ID) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index fbe88df..7addc8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -24,6 +24,7 @@
 import android.graphics.Point;
 import android.util.AttributeSet;
 import android.util.MathUtils;
+import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
@@ -492,12 +493,9 @@
         if (animationListener != null) {
             mAppearAnimator.addListener(animationListener);
         }
-        if (delay > 0) {
-            // we need to apply the initial state already to avoid drawn frames in the wrong state
-            updateAppearAnimationAlpha();
-            updateAppearRect();
-            mAppearAnimator.setStartDelay(delay);
-        }
+        // we need to apply the initial state already to avoid drawn frames in the wrong state
+        updateAppearAnimationAlpha();
+        updateAppearRect();
         mAppearAnimator.addListener(new AnimatorListenerAdapter() {
             private boolean mWasCancelled;
 
@@ -528,7 +526,20 @@
                 mWasCancelled = true;
             }
         });
-        mAppearAnimator.start();
+
+        // Cache the original animator so we can check if the animation should be started in the
+        // Choreographer callback. It's possible that the original animator (mAppearAnimator) is
+        // replaced with a new value before the callback is called.
+        ValueAnimator cachedAnimator = mAppearAnimator;
+        // Even when delay=0, starting the animation on the next frame is necessary to avoid jank.
+        // Not doing so will increase the chances our Animator will be forced to skip a value of
+        // the animation's progression, causing stutter.
+        Choreographer.getInstance().postFrameCallbackDelayed(
+                frameTimeNanos -> {
+                    if (mAppearAnimator == cachedAnimator) {
+                        mAppearAnimator.start();
+                    }
+                }, delay);
     }
 
     private int getCujType(boolean isAppearing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9f50aef..a6b71dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -79,6 +79,8 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -177,6 +179,7 @@
     private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     private Optional<BubblesManager> mBubblesManagerOptional;
     private MetricsLogger mMetricsLogger;
+    private FeatureFlags mFeatureFlags;
     private int mIconTransformContentShift;
     private int mMaxHeadsUpHeightBeforeN;
     private int mMaxHeadsUpHeightBeforeP;
@@ -277,7 +280,7 @@
     private boolean mChildIsExpanding;
 
     private boolean mJustClicked;
-    private boolean mIconAnimationRunning;
+    private boolean mAnimationRunning;
     private boolean mShowNoBackground;
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
@@ -451,10 +454,26 @@
         return mPublicLayout;
     }
 
-    public void setIconAnimationRunning(boolean running) {
-        for (NotificationContentView l : mLayouts) {
-            setIconAnimationRunning(running, l);
+    /**
+     * Sets animations running in the layouts of this row, including public, private, and children.
+     * @param running whether the animations should be started running or stopped.
+     */
+    public void setAnimationRunning(boolean running) {
+        // Sets animations running in the private/public layouts.
+        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) {
+            for (NotificationContentView l : mLayouts) {
+                if (l != null) {
+                    l.setContentAnimationRunning(running);
+                    setIconAnimationRunning(running, l);
+                }
+            }
+        } else {
+            for (NotificationContentView l : mLayouts) {
+                setIconAnimationRunning(running, l);
+            }
         }
+        // For groups summaries with children, we want to set the children containers
+        // animating as well.
         if (mIsSummaryWithChildren) {
             NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper();
             if (viewWrapper != null) {
@@ -468,12 +487,18 @@
                     mChildrenContainer.getAttachedChildren();
             for (int i = 0; i < notificationChildren.size(); i++) {
                 ExpandableNotificationRow child = notificationChildren.get(i);
-                child.setIconAnimationRunning(running);
+                child.setAnimationRunning(running);
             }
         }
-        mIconAnimationRunning = running;
+        mAnimationRunning = running;
     }
 
+    /**
+     * Starts or stops animations of the icons in all potential content views (regardless of
+     * whether they're contracted, expanded, etc).
+     *
+     * @param running whether to start or stop the icon's animation.
+     */
     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
         if (layout != null) {
             View contractedChild = layout.getContractedChild();
@@ -485,16 +510,29 @@
         }
     }
 
+    /**
+     * Starts or stops animations of the icon in the provided view's icon and right icon.
+     *
+     * @param running whether to start or stop the icon's animation.
+     * @param child   the view with the icon to start or stop.
+     */
     private void setIconAnimationRunningForChild(boolean running, View child) {
         if (child != null) {
             ImageView icon = child.findViewById(com.android.internal.R.id.icon);
-            setIconRunning(icon, running);
+            setImageViewAnimationRunning(icon, running);
             ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon);
-            setIconRunning(rightIcon, running);
+            setImageViewAnimationRunning(rightIcon, running);
         }
     }
 
-    private void setIconRunning(ImageView imageView, boolean running) {
+    /**
+     * Starts or stops the animation of a provided image view if it's an AnimationDrawable or an
+     * AnimatedVectorDrawable.
+     *
+     * @param imageView the image view on which to start/stop animation.
+     * @param running   whether to start or stop the view's animation.
+     */
+    private void setImageViewAnimationRunning(ImageView imageView, boolean running) {
         if (imageView != null) {
             Drawable drawable = imageView.getDrawable();
             if (drawable instanceof AnimationDrawable) {
@@ -553,6 +591,7 @@
         }
         mShowingPublicInitialized = false;
         updateNotificationColor();
+        updateLongClickable();
         if (mMenuRow != null) {
             mMenuRow.onNotificationUpdated(mEntry.getSbn());
             mMenuRow.setAppName(mAppName);
@@ -561,8 +600,8 @@
             mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
             mChildrenContainer.onNotificationUpdated();
         }
-        if (mIconAnimationRunning) {
-            setIconAnimationRunning(true);
+        if (mAnimationRunning) {
+            setAnimationRunning(true);
         }
         if (mLastChronometerRunning) {
             setChronometerRunning(true);
@@ -1038,7 +1077,7 @@
             notifyHeightChanged(false /* needsAnimation */);
         }
         if (pinned) {
-            setIconAnimationRunning(true);
+            setAnimationRunning(true);
             mExpandedWhenPinned = false;
         } else if (mExpandedWhenPinned) {
             setUserExpanded(true);
@@ -1158,8 +1197,26 @@
         return getShowingLayout().getVisibleWrapper();
     }
 
+    private boolean isNotificationRowLongClickable() {
+        if (mLongPressListener == null) {
+            return false;
+        }
+
+        if (!areGutsExposed()) { // guts is not opened
+            return true;
+        }
+
+        // if it is leave behind, it shouldn't be long clickable.
+        return !isGutsLeaveBehind();
+    }
+
+    private void updateLongClickable() {
+        setLongClickable(isNotificationRowLongClickable());
+    }
+
     public void setLongPressListener(LongPressListener longPressListener) {
         mLongPressListener = longPressListener;
+        updateLongClickable();
     }
 
     public void setDragController(ExpandableNotificationRowDragController dragController) {
@@ -1627,6 +1684,11 @@
         );
     }
 
+    /**
+     * Constructs an ExpandableNotificationRow.
+     * @param context context passed to image resolver
+     * @param attrs attributes used to initialize parent view
+     */
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
         mImageResolver = new NotificationInlineImageResolver(context,
@@ -1662,7 +1724,8 @@
             NotificationGutsManager gutsManager,
             MetricsLogger metricsLogger,
             SmartReplyConstants smartReplyConstants,
-            SmartReplyController smartReplyController) {
+            SmartReplyController smartReplyController,
+            FeatureFlags featureFlags) {
         mEntry = entry;
         mAppName = appName;
         if (mMenuRow == null) {
@@ -1697,6 +1760,7 @@
         mBubblesManagerOptional = bubblesManagerOptional;
         mNotificationGutsManager = gutsManager;
         mMetricsLogger = metricsLogger;
+        mFeatureFlags = featureFlags;
     }
 
     private void initDimens() {
@@ -1999,11 +2063,13 @@
     void onGutsOpened() {
         resetTranslation();
         updateContentAccessibilityImportanceForGuts(false /* isEnabled */);
+        updateLongClickable();
     }
 
     void onGutsClosed() {
         updateContentAccessibilityImportanceForGuts(true /* isEnabled */);
         mIsSnoozed = false;
+        updateLongClickable();
     }
 
     /**
@@ -2902,6 +2968,10 @@
         return (mGuts != null && mGuts.isExposed());
     }
 
+    private boolean isGutsLeaveBehind() {
+        return (mGuts != null && mGuts.isLeavebehind());
+    }
+
     @Override
     public boolean isContentExpandable() {
         if (mIsSummaryWithChildren && !shouldShowPublic()) {
@@ -3588,11 +3658,13 @@
     @VisibleForTesting
     protected void setPrivateLayout(NotificationContentView privateLayout) {
         mPrivateLayout = privateLayout;
+        mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
     }
 
     @VisibleForTesting
     protected void setPublicLayout(NotificationContentView publicLayout) {
         mPublicLayout = publicLayout;
+        mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index d113860..bb92dfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -219,7 +219,8 @@
                 mNotificationGutsManager,
                 mMetricsLogger,
                 mSmartReplyConstants,
-                mSmartReplyController
+                mSmartReplyController,
+                mFeatureFlags
         );
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (mAllowLongPress) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 49dc655..21f4cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -16,15 +16,22 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
 import android.view.View;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -41,6 +48,11 @@
     private String mManageNotificationText;
     private String mManageNotificationHistoryText;
 
+    // Footer label
+    private TextView mSeenNotifsFooterTextView;
+    private @StringRes int mSeenNotifsFilteredText;
+    private int mUnlockIconSize;
+
     public FooterView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
@@ -73,10 +85,41 @@
         super.onFinishInflate();
         mClearAllButton = (FooterViewButton) findSecondaryView();
         mManageButton = findViewById(R.id.manage_text);
+        mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
         updateResources();
         updateText();
     }
 
+    public void setFooterLabelTextAndIcon(@StringRes int text, @DrawableRes int icon) {
+        mSeenNotifsFilteredText = text;
+        if (mSeenNotifsFilteredText != 0) {
+            mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
+        } else {
+            mSeenNotifsFooterTextView.setText(null);
+        }
+        Drawable drawable;
+        if (icon == 0) {
+            drawable = null;
+        } else {
+            drawable = getResources().getDrawable(icon);
+            drawable.setBounds(0, 0, mUnlockIconSize, mUnlockIconSize);
+        }
+        mSeenNotifsFooterTextView.setCompoundDrawablesRelative(drawable, null, null, null);
+        updateFooterVisibilityMode();
+    }
+
+    private void updateFooterVisibilityMode() {
+        if (mSeenNotifsFilteredText != 0) {
+            mManageButton.setVisibility(View.GONE);
+            mClearAllButton.setVisibility(View.GONE);
+            mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+        } else {
+            mManageButton.setVisibility(View.VISIBLE);
+            mClearAllButton.setVisibility(View.VISIBLE);
+            mSeenNotifsFooterTextView.setVisibility(View.GONE);
+        }
+    }
+
     public void setManageButtonClickListener(OnClickListener listener) {
         mManageButton.setOnClickListener(listener);
     }
@@ -135,12 +178,19 @@
         mClearAllButton.setTextColor(textColor);
         mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
         mManageButton.setTextColor(textColor);
+        final @ColorInt int labelTextColor =
+                Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
+        mSeenNotifsFooterTextView.setTextColor(labelTextColor);
+        mSeenNotifsFooterTextView.setCompoundDrawableTintList(
+                ColorStateList.valueOf(labelTextColor));
     }
 
     private void updateResources() {
         mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
         mManageNotificationHistoryText = getContext()
                 .getString(R.string.manage_notifications_history_text);
+        mUnlockIconSize = getResources()
+                .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index e46bf52..4a023c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -184,6 +184,8 @@
     private boolean mRemoteInputVisible;
     private int mUnrestrictedContentHeight;
 
+    private boolean mContentAnimating;
+
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mHybridGroupManager = new HybridGroupManager(getContext());
@@ -2129,8 +2131,49 @@
         return false;
     }
 
+    /**
+     * Starts and stops animations in the underlying views.
+     * Avoids restarting the animations by checking whether they're already running first.
+     * Return value is used for testing.
+     *
+     * @param running whether to start animations running, or stop them.
+     * @return true if the state of animations changed.
+     */
+    public boolean setContentAnimationRunning(boolean running) {
+        boolean stateChangeRequired = (running != mContentAnimating);
+        if (stateChangeRequired) {
+            // Starts or stops the animations in the potential views.
+            if (mContractedWrapper != null) {
+                mContractedWrapper.setAnimationsRunning(running);
+            }
+            if (mExpandedWrapper != null) {
+                mExpandedWrapper.setAnimationsRunning(running);
+            }
+            if (mHeadsUpWrapper != null) {
+                mHeadsUpWrapper.setAnimationsRunning(running);
+            }
+            // Updates the state tracker.
+            mContentAnimating = running;
+            return true;
+        }
+        return false;
+    }
+
     private static class RemoteInputViewData {
         @Nullable RemoteInputView mView;
         @Nullable RemoteInputViewController mController;
     }
+
+    @VisibleForTesting
+    protected void setContractedWrapper(NotificationViewWrapper contractedWrapper) {
+        mContractedWrapper = contractedWrapper;
+    }
+    @VisibleForTesting
+    protected void setExpandedWrapper(NotificationViewWrapper expandedWrapper) {
+        mExpandedWrapper = expandedWrapper;
+    }
+    @VisibleForTesting
+    protected void setHeadsUpWrapper(NotificationViewWrapper headsUpWrapper) {
+        mHeadsUpWrapper = headsUpWrapper;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index ea12b82..efcbb3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -44,12 +44,10 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.Dependency;
-import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -65,28 +63,29 @@
 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener;
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
-import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.wmshell.BubblesManager;
 
-import java.io.PrintWriter;
 import java.util.Optional;
 
+import javax.inject.Inject;
+
 import dagger.Lazy;
 
 /**
  * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
  * closing guts, and keeping track of the currently exposed notification guts.
  */
+@SysUISingleton
 public class NotificationGutsManager implements NotifGutsViewManager {
     private static final String TAG = "NotificationGutsManager";
 
     // Must match constant in Settings. Used to highlight preferences when linking to Settings.
     private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
 
-    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    private final MetricsLogger mMetricsLogger;
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
     private final HighPriorityProvider mHighPriorityProvider;
@@ -94,12 +93,9 @@
     private final OnUserInteractionCallback mOnUserInteractionCallback;
 
     // Dependencies:
-    private final NotificationLockscreenUserManager mLockscreenUserManager =
-            Dependency.get(NotificationLockscreenUserManager.class);
-    private final StatusBarStateController mStatusBarStateController =
-            Dependency.get(StatusBarStateController.class);
-    private final DeviceProvisionedController mDeviceProvisionedController =
-            Dependency.get(DeviceProvisionedController.class);
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final StatusBarStateController mStatusBarStateController;
+    private final DeviceProvisionedController mDeviceProvisionedController;
     private final AssistantFeedbackController mAssistantFeedbackController;
 
     // which notification is currently being longpress-examined by the user
@@ -124,9 +120,7 @@
     private final ShadeController mShadeController;
     private NotifGutsViewListener mGutsListener;
 
-    /**
-     * Injected constructor. See {@link NotificationsModule}.
-     */
+    @Inject
     public NotificationGutsManager(Context context,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             @Main Handler mainHandler,
@@ -143,7 +137,11 @@
             Optional<BubblesManager> bubblesManagerOptional,
             UiEventLogger uiEventLogger,
             OnUserInteractionCallback onUserInteractionCallback,
-            ShadeController shadeController) {
+            ShadeController shadeController,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            StatusBarStateController statusBarStateController,
+            DeviceProvisionedController deviceProvisionedController,
+            MetricsLogger metricsLogger) {
         mContext = context;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
         mMainHandler = mainHandler;
@@ -161,6 +159,10 @@
         mUiEventLogger = uiEventLogger;
         mOnUserInteractionCallback = onUserInteractionCallback;
         mShadeController = shadeController;
+        mLockscreenUserManager = notificationLockscreenUserManager;
+        mStatusBarStateController = statusBarStateController;
+        mDeviceProvisionedController = deviceProvisionedController;
+        mMetricsLogger = metricsLogger;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -372,7 +374,8 @@
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 row.getIsNonblockable(),
                 mHighPriorityProvider.isHighPriority(row.getEntry()),
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
     }
 
     /**
@@ -583,7 +586,9 @@
         }
 
         final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-        view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        if (view.isLongClickable()) {
+            view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        }
         if (row.areGutsExposed()) {
             closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
                     true /* removeControls */, -1 /* x */, -1 /* y */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index ea0060a..8a50f2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -204,10 +204,11 @@
             boolean isDeviceProvisioned,
             boolean isNonblockable,
             boolean wasShownHighPriority,
-            AssistantFeedbackController assistantFeedbackController)
+            AssistantFeedbackController assistantFeedbackController,
+            MetricsLogger metricsLogger)
             throws RemoteException {
         mINotificationManager = iNotificationManager;
-        mMetricsLogger = Dependency.get(MetricsLogger.class);
+        mMetricsLogger = metricsLogger;
         mOnUserInteractionCallback = onUserInteractionCallback;
         mChannelEditorDialogController = channelEditorDialogController;
         mAssistantFeedbackController = assistantFeedbackController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
index 8732696..175ba15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
@@ -18,11 +18,15 @@
 
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 import android.view.View;
 
+import com.android.internal.R;
+import com.android.internal.widget.BigPictureNotificationImageView;
 import com.android.systemui.statusbar.notification.ImageTransformState;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -31,6 +35,8 @@
  */
 public class NotificationBigPictureTemplateViewWrapper extends NotificationTemplateViewWrapper {
 
+    private BigPictureNotificationImageView mImageView;
+
     protected NotificationBigPictureTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -39,9 +45,14 @@
     @Override
     public void onContentUpdated(ExpandableNotificationRow row) {
         super.onContentUpdated(row);
+        resolveViews();
         updateImageTag(row.getEntry().getSbn());
     }
 
+    private void resolveViews() {
+        mImageView = mView.findViewById(R.id.big_picture);
+    }
+
     private void updateImageTag(StatusBarNotification sbn) {
         final Bundle extras = sbn.getNotification().extras;
         Icon bigLargeIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG, Icon.class);
@@ -54,4 +65,25 @@
             mRightIcon.setTag(ImageTransformState.ICON_TAG, getLargeIcon(sbn.getNotification()));
         }
     }
+
+    /**
+     * Starts or stops the animations in any drawables contained in this BigPicture Notification.
+     *
+     * @param running Whether the animations should be set to run.
+     */
+    @Override
+    public void setAnimationsRunning(boolean running) {
+        if (mImageView == null) {
+            return;
+        }
+        Drawable d = mImageView.getDrawable();
+        if (d instanceof AnimatedImageDrawable) {
+            AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
+            if (running) {
+                animatedImageDrawable.start();
+            } else {
+                animatedImageDrawable.stop();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index e136055..10753f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -17,16 +17,20 @@
 package com.android.systemui.statusbar.notification.row.wrapper
 
 import android.content.Context
+import android.graphics.drawable.AnimatedImageDrawable
 import android.view.View
 import android.view.ViewGroup
 import com.android.internal.widget.CachingIconView
 import com.android.internal.widget.ConversationLayout
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
 import com.android.internal.widget.MessagingLinearLayout
 import com.android.systemui.R
 import com.android.systemui.statusbar.notification.NotificationFadeAware
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform
+import com.android.systemui.util.children
 
 /**
  * Wraps a notification containing a conversation template
@@ -49,6 +53,7 @@
     private lateinit var expandBtn: View
     private lateinit var expandBtnContainer: View
     private lateinit var imageMessageContainer: ViewGroup
+    private lateinit var messageContainers: ArrayList<MessagingGroup>
     private lateinit var messagingLinearLayout: MessagingLinearLayout
     private lateinit var conversationTitleView: View
     private lateinit var importanceRing: View
@@ -60,6 +65,7 @@
     private fun resolveViews() {
         messagingLinearLayout = conversationLayout.messagingLinearLayout
         imageMessageContainer = conversationLayout.imageMessageContainer
+        messageContainers = conversationLayout.messagingGroups
         with(conversationLayout) {
             conversationIconContainer =
                     requireViewById(com.android.internal.R.id.conversation_icon_container)
@@ -146,4 +152,26 @@
         NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded)
         NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded)
     }
+
+    // Starts or stops the animations in any drawables contained in this Conversation Notification.
+    override fun setAnimationsRunning(running: Boolean) {
+        // We apply to both the child message containers in a conversation group,
+        // and the top level image message container.
+        val containers = messageContainers.asSequence().map { it.messageContainer } +
+                sequenceOf(imageMessageContainer)
+        val drawables =
+                containers
+                        .flatMap { it.children }
+                        .mapNotNull { child ->
+                            (child as? MessagingImageMessage)?.let { imageMessage ->
+                                imageMessage.drawable as? AnimatedImageDrawable
+                            }
+                        }
+        drawables.toSet().forEach {
+            when {
+                running -> it.start()
+                !running -> it.stop()
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
index c587ce0..4592fde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
@@ -17,9 +17,13 @@
 package com.android.systemui.statusbar.notification.row.wrapper;
 
 import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingLayout;
 import com.android.internal.widget.MessagingLinearLayout;
 import com.android.systemui.R;
@@ -127,4 +131,40 @@
         }
         return super.getMinLayoutHeight();
     }
+
+    /**
+     * Starts or stops the animations in any drawables contained in this Messaging Notification.
+     *
+     * @param running Whether the animations should be set to run.
+     */
+    @Override
+    public void setAnimationsRunning(boolean running) {
+        if (mMessagingLayout == null) {
+            return;
+        }
+
+        for (MessagingGroup group : mMessagingLayout.getMessagingGroups()) {
+            for (int i = 0; i < group.getMessageContainer().getChildCount(); i++) {
+                View view = group.getMessageContainer().getChildAt(i);
+                // We only need to set animations in MessagingImageMessages.
+                if (!(view instanceof MessagingImageMessage)) {
+                    continue;
+                }
+                MessagingImageMessage imageMessage =
+                        (com.android.internal.widget.MessagingImageMessage) view;
+
+                // If the drawable isn't an AnimatedImageDrawable, we can't set it to animate.
+                Drawable d = imageMessage.getDrawable();
+                if (!(d instanceof AnimatedImageDrawable)) {
+                    continue;
+                }
+                AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
+                if (running) {
+                    animatedImageDrawable.start();
+                } else {
+                    animatedImageDrawable.stop();
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 1c22f09..ff5b9cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -403,4 +403,12 @@
         NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded);
         NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded);
     }
+
+    /**
+     * Starts or stops the animations in any drawables contained in this Notification.
+     *
+     * @param running Whether the animations should be set to run.
+     */
+    public void setAnimationsRunning(boolean running) {
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 356ddfa..1fb7eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -147,7 +147,7 @@
     private boolean mShadeNeedsToClose = false;
 
     @VisibleForTesting
-    static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
+    static final float RUBBER_BAND_FACTOR_NORMAL = 0.1f;
     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
     private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
     /**
@@ -538,6 +538,7 @@
     private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private boolean mShouldUseSplitNotificationShade;
+    private boolean mHasFilteredOutSeenNotifications;
 
     private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
             new ExpandableView.OnHeightChangedListener() {
@@ -684,6 +685,10 @@
         updateFooter();
     }
 
+    void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
+        mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
+    }
+
     @VisibleForTesting
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooter() {
@@ -3132,7 +3137,7 @@
     private void updateAnimationState(boolean running, View child) {
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            row.setIconAnimationRunning(running);
+            row.setAnimationRunning(running);
         }
     }
 
@@ -4612,13 +4617,12 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    void updateEmptyShadeView(
-            boolean visible, boolean areNotificationsHiddenInShade, boolean areSeenNotifsFiltered) {
+    void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
         if (areNotificationsHiddenInShade) {
             updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
-        } else if (areSeenNotifsFiltered) {
+        } else if (mHasFilteredOutSeenNotifications) {
             updateEmptyShadeView(
                     R.string.no_unseen_notif_text,
                     R.string.unlock_to_see_notif_text,
@@ -4657,13 +4661,20 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
-        if (mFooterView == null) {
+        if (mFooterView == null || mNotificationStackSizeCalculator == null) {
             return;
         }
         boolean animate = mIsExpanded && mAnimationsEnabled;
         mFooterView.setVisible(visible, animate);
         mFooterView.setSecondaryVisible(showDismissView, animate);
         mFooterView.showHistory(showHistory);
+        if (mHasFilteredOutSeenNotifications) {
+            mFooterView.setFooterLabelTextAndIcon(
+                    R.string.unlock_to_see_notif_text,
+                    R.drawable.ic_friction_lock_closed);
+        } else {
+            mFooterView.setFooterLabelTextAndIcon(0, 0);
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 5891948..42d122d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -120,6 +120,7 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.Compile;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -189,6 +190,7 @@
     private final FeatureFlags mFeatureFlags;
     private final boolean mUseRoundnessSourceTypes;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
+    private final SecureSettings mSecureSettings;
 
     private View mLongPressedView;
 
@@ -667,7 +669,8 @@
             NotificationStackScrollLogger logger,
             NotificationStackSizeCalculator notificationStackSizeCalculator,
             FeatureFlags featureFlags,
-            NotificationTargetsHelper notificationTargetsHelper) {
+            NotificationTargetsHelper notificationTargetsHelper,
+            SecureSettings secureSettings) {
         mStackStateLogger = stackLogger;
         mLogger = logger;
         mAllowLongPress = allowLongPress;
@@ -709,6 +712,7 @@
         mFeatureFlags = featureFlags;
         mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mNotificationTargetsHelper = notificationTargetsHelper;
+        mSecureSettings = secureSettings;
         updateResources();
     }
 
@@ -1015,8 +1019,7 @@
                 Log.wtf(TAG, "isHistoryEnabled failed to initialize its value");
                 return false;
             }
-            mHistoryEnabled = historyEnabled = Settings.Secure.getIntForUser(
-                    mView.getContext().getContentResolver(),
+            mHistoryEnabled = historyEnabled = mSecureSettings.getIntForUser(
                     Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
                     0,
                     UserHandle.USER_CURRENT) == 1;
@@ -1242,11 +1245,7 @@
                 // For more details, see: b/228790482
                 && !isInTransitionToKeyguard();
 
-        mView.updateEmptyShadeView(
-                shouldShow,
-                mZenModeController.areNotificationsHiddenInShade(),
-                mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
-                        && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
+        mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
 
         Trace.endSection();
     }
@@ -1942,6 +1941,9 @@
         @Override
         public void setNotifStats(@NonNull NotifStats notifStats) {
             mNotifStats = notifStats;
+            mView.setHasFilteredOutSeenNotifications(
+                    mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
+                            && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
             updateFooter();
             updateShowEmptyShadeView();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 149ec54..8b1a02b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -279,7 +279,8 @@
                 public void onManagedProfileChanged() {
                     if (mManagedProfileController.hasActiveProfile()) {
                         if (mAutoTracker.isAdded(WORK)) return;
-                        mHost.addTile(WORK);
+                        final int position = mAutoTracker.getRestoredTilePosition(WORK);
+                        mHost.addTile(WORK, position);
                         mAutoTracker.setTileAdded(WORK);
                     } else {
                         if (!mAutoTracker.isAdded(WORK)) return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 2dad8e0..ccde3c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -31,7 +31,6 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -55,6 +54,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
@@ -99,6 +99,7 @@
     private final Optional<Vibrator> mVibratorOptional;
     private final DisableFlagsLogger mDisableFlagsLogger;
     private final int mDisplayId;
+    private final UserTracker mUserTracker;
     private final boolean mVibrateOnOpening;
     private final VibrationEffect mCameraLaunchGestureVibrationEffect;
     private final SystemBarAttributesListener mSystemBarAttributesListener;
@@ -133,7 +134,8 @@
             DisableFlagsLogger disableFlagsLogger,
             @DisplayId int displayId,
             SystemBarAttributesListener systemBarAttributesListener,
-            Lazy<CameraLauncher> cameraLauncherLazy) {
+            Lazy<CameraLauncher> cameraLauncherLazy,
+            UserTracker userTracker) {
         mCentralSurfaces = centralSurfaces;
         mContext = context;
         mShadeController = shadeController;
@@ -157,6 +159,7 @@
         mDisableFlagsLogger = disableFlagsLogger;
         mDisplayId = displayId;
         mCameraLauncherLazy = cameraLauncherLazy;
+        mUserTracker = userTracker;
 
         mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
         mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -215,7 +218,7 @@
             return;
         }
 
-        mNotificationPanelViewController.expandWithoutQs();
+        mNotificationPanelViewController.expandShadeToNotifications();
     }
 
     @Override
@@ -375,7 +378,7 @@
             mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
                     false /* onlyProvisioned */, true /* dismissShade */,
                     true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
-                    null /* animationController */, UserHandle.CURRENT);
+                    null /* animationController */, mUserTracker.getUserHandle());
         } else {
             if (!mCentralSurfaces.isDeviceInteractive()) {
                 // Avoid flickering of the scrim when we instant launch the camera and the bouncer
@@ -432,7 +435,7 @@
             mCentralSurfaces.startActivityDismissingKeyguard(emergencyIntent,
                     false /* onlyProvisioned */, true /* dismissShade */,
                     true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
-                    null /* animationController */, UserHandle.CURRENT);
+                    null /* animationController */, mUserTracker.getUserHandle());
             return;
         }
 
@@ -447,7 +450,7 @@
             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 mStatusBarKeyguardViewManager.reset(true /* hide */);
             }
-            mContext.startActivityAsUser(emergencyIntent, UserHandle.CURRENT);
+            mContext.startActivityAsUser(emergencyIntent, mUserTracker.getUserHandle());
             return;
         }
         // We need to defer the emergency action launch until the screen comes on, since otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index f1e1f42..85399ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -64,7 +64,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
@@ -180,6 +179,7 @@
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.scrim.ScrimView;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -292,26 +292,9 @@
 
     private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
 
-    /**
-     * If true, the system is in the half-boot-to-decryption-screen state.
-     * Prudently disable QS and notifications.
-     */
-    public static final boolean ONLY_CORE_APPS;
-
-    static {
-        boolean onlyCoreApps;
-        try {
-            IPackageManager packageManager =
-                    IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-            onlyCoreApps = packageManager != null && packageManager.isOnlyCoreApps();
-        } catch (RemoteException e) {
-            onlyCoreApps = false;
-        }
-        ONLY_CORE_APPS = onlyCoreApps;
-    }
-
     private final Context mContext;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    private final DeviceStateManager mDeviceStateManager;
     private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
     private float mTransitionToFullShadeProgress = 0f;
     private NotificationListContainer mNotifListContainer;
@@ -523,6 +506,7 @@
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final MessageRouter mMessageRouter;
     private final WallpaperManager mWallpaperManager;
+    private final UserTracker mUserTracker;
 
     private CentralSurfacesComponent mCentralSurfacesComponent;
 
@@ -766,7 +750,8 @@
             IDreamManager dreamManager,
             Lazy<CameraLauncher> cameraLauncherLazy,
             Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy,
-            AlternateBouncerInteractor alternateBouncerInteractor
+            AlternateBouncerInteractor alternateBouncerInteractor,
+            UserTracker userTracker
     ) {
         mContext = context;
         mNotificationsController = notificationsController;
@@ -846,6 +831,7 @@
         mJankMonitor = jankMonitor;
         mCameraLauncherLazy = cameraLauncherLazy;
         mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mUserTracker = userTracker;
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mStartingSurfaceOptional = startingSurfaceOptional;
@@ -879,11 +865,15 @@
         mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT,
                 id -> onLaunchTransitionTimeout());
 
-        deviceStateManager.registerCallback(mMainExecutor,
-                new FoldStateListener(mContext, this::onFoldedStateChanged));
+        mDeviceStateManager = deviceStateManager;
         wiredChargingRippleController.registerCallbacks();
 
         mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
+
+        // Based on teamfood flag, turn predictive back dispatch on at runtime.
+        if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
+            mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
+        }
     }
 
     @Override
@@ -998,11 +988,6 @@
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy.init();
 
-        // Based on teamfood flag, turn predictive back dispatch on at runtime.
-        if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
-            mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
-        }
-
         mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onUnlockedChanged() {
@@ -1069,6 +1054,8 @@
             }
         });
 
+        registerCallbacks();
+
         mFalsingManager.addFalsingBeliefListener(mFalsingBeliefListener);
 
         mPluginManager.addPluginListener(
@@ -1124,6 +1111,14 @@
     }
 
     @VisibleForTesting
+    /** Registers listeners/callbacks with external dependencies. */
+    void registerCallbacks() {
+        //TODO(b/264502026) move the rest of the listeners here.
+        mDeviceStateManager.registerCallback(mMainExecutor,
+                new FoldStateListener(mContext, this::onFoldedStateChanged));
+    }
+
+    @VisibleForTesting
     void initShadeVisibilityListener() {
         mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
             @Override
@@ -1311,8 +1306,13 @@
         // Set up the quick settings tile panel
         final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
         if (container != null) {
-            FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
-            ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
+            FragmentHostManager fragmentHostManager =
+                    mFragmentService.getFragmentHostManager(container);
+            ExtensionFragmentListener.attachExtensonToFragment(
+                    mFragmentService,
+                    container,
+                    QS.TAG,
+                    R.id.qs_frame,
                     mExtensionController
                             .newExtension(QS.class)
                             .withPlugin(QS.class)
@@ -1404,6 +1404,7 @@
         // Things that mean we're not swiping to dismiss the keyguard, and should ignore this
         // expansion:
         // - Keyguard isn't even visible.
+        // - We're swiping on the bouncer, not the lockscreen.
         // - Keyguard is occluded. Expansion changes here are the shade being expanded over the
         //   occluding activity.
         // - Keyguard is visible, but can't be dismissed (swiping up will show PIN/password prompt).
@@ -1412,11 +1413,14 @@
         //   to dismiss the lock screen until entering the SIM PIN.
         // - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the
         //   keyguard.
+        // - Shade is in QQS over keyguard - swiping up should take us back to keyguard
         if (!isKeyguardShowing()
+                || mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
                 || isOccluded()
                 || !mKeyguardStateController.canDismissLockScreen()
                 || mKeyguardViewMediator.isAnySimPinSecure()
-                || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)) {
+                || (mNotificationPanelViewController.isQsExpanded() && trackingTouch)
+                || mNotificationPanelViewController.getBarState() == StatusBarState.SHADE_LOCKED) {
             return;
         }
 
@@ -1479,7 +1483,9 @@
     }
 
     protected QS createDefaultQSFragment() {
-        return FragmentHostManager.get(mNotificationShadeWindowView).create(QSFragment.class);
+        return mFragmentService
+                .getFragmentHostManager(mNotificationShadeWindowView)
+                .create(QSFragment.class);
     }
 
     private void setUpPresenter() {
@@ -1692,8 +1698,7 @@
                         || !mUserSwitcherController.isSimpleUserSwitcher())
                 && !isShadeDisabled()
                 && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
-                && !mDozing
-                && !ONLY_CORE_APPS;
+                && !mDozing;
         mNotificationPanelViewController.setQsExpansionEnabledPolicy(expandEnabled);
         Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled);
     }
@@ -3774,6 +3779,9 @@
             });
         } else if (mDozing && !unlocking) {
             mScrimController.transitionTo(ScrimState.AOD);
+            // This will cancel the keyguardFadingAway animation if it is running. We need to do
+            // this as otherwise it can remain pending and leave keyguard in a weird state.
+            mUnlockScrimCallback.onCancelled();
         } else if (mKeyguardStateController.isShowing() && !isOccluded() && !unlocking) {
             mScrimController.transitionTo(ScrimState.KEYGUARD);
         } else if (mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming()
@@ -4162,7 +4170,7 @@
                 Log.wtf(TAG, "WallpaperManager not supported");
                 return;
             }
-            WallpaperInfo info = mWallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
+            WallpaperInfo info = mWallpaperManager.getWallpaperInfo(mUserTracker.getUserId());
             mWallpaperController.onWallpaperInfoUpdated(info);
 
             final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
@@ -4386,6 +4394,6 @@
                 return new UserHandle(UserHandle.myUserId());
             }
         }
-        return UserHandle.CURRENT;
+        return mUserTracker.getUserHandle();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 0446cef..b88531e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -45,6 +45,7 @@
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -84,6 +85,7 @@
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final FoldAodAnimationController mFoldAodAnimationController;
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private final UserTracker mUserTracker;
 
     private final Set<Callback> mCallbacks = new HashSet<>();
 
@@ -128,7 +130,8 @@
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ConfigurationController configurationController,
-            StatusBarStateController statusBarStateController) {
+            StatusBarStateController statusBarStateController,
+            UserTracker userTracker) {
         mResources = resources;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -140,6 +143,7 @@
         mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
         mScreenOffAnimationController = screenOffAnimationController;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        mUserTracker = userTracker;
 
         keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
         tunerService.addTunable(
@@ -169,7 +173,7 @@
 
     private void updateQuickPickupEnabled() {
         mIsQuickPickupEnabled =
-                mAmbientDisplayConfiguration.quickPickupSensorEnabled(UserHandle.USER_CURRENT);
+                mAmbientDisplayConfiguration.quickPickupSensorEnabled(mUserTracker.getUserId());
     }
 
     public boolean getDisplayStateSupported() {
@@ -348,7 +352,7 @@
     }
 
     private boolean willAnimateFromLockScreenToAod() {
-        return getAlwaysOn() && mKeyguardVisible;
+        return shouldControlScreenOff() && mKeyguardVisible;
     }
 
     private boolean getBoolean(String propName, int resId) {
@@ -418,7 +422,7 @@
 
     @Override
     public void onTuningChanged(String key, String newValue) {
-        mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
+        mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(mUserTracker.getUserId());
 
         if (key.equals(Settings.Secure.DOZE_ALWAYS_ON)) {
             updateControlScreenOff();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
deleted file mode 100644
index f08de85..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ /dev/null
@@ -1,756 +0,0 @@
-/*
- * Copyright (C) 2014 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.systemui.statusbar.phone;
-
-import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.Handler;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-
-import com.android.internal.policy.SystemBarUtils;
-import com.android.keyguard.KeyguardHostViewController;
-import com.android.keyguard.KeyguardSecurityModel;
-import com.android.keyguard.KeyguardSecurityView;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.ViewMediatorCallback;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.ListenerSet;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * A class which manages the primary (pin/pattern/password) bouncer on the lockscreen.
- * @deprecated Use KeyguardBouncerRepository
- */
-@Deprecated
-public class KeyguardBouncer {
-
-    private static final String TAG = "PrimaryKeyguardBouncer";
-    static final long BOUNCER_FACE_DELAY = 1200;
-    public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
-    protected final Context mContext;
-    protected final ViewMediatorCallback mCallback;
-    protected final ViewGroup mContainer;
-    private final FalsingCollector mFalsingCollector;
-    private final DismissCallbackRegistry mDismissCallbackRegistry;
-    private final Handler mHandler;
-    private final List<PrimaryBouncerExpansionCallback> mExpansionCallbacks = new ArrayList<>();
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final KeyguardStateController mKeyguardStateController;
-    private final KeyguardSecurityModel mKeyguardSecurityModel;
-    private final KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
-    private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
-            new KeyguardUpdateMonitorCallback() {
-                @Override
-                public void onStrongAuthStateChanged(int userId) {
-                    mBouncerPromptReason = mCallback.getBouncerPromptReason();
-                }
-
-                @Override
-                public void onLockedOutStateChanged(BiometricSourceType type) {
-                    if (type == BiometricSourceType.FINGERPRINT) {
-                        mBouncerPromptReason = mCallback.getBouncerPromptReason();
-                    }
-                }
-
-                @Override
-                public void onNonStrongBiometricAllowedChanged(int userId) {
-                    mBouncerPromptReason = mCallback.getBouncerPromptReason();
-                }
-            };
-    private final Runnable mRemoveViewRunnable = this::removeView;
-    private final KeyguardBypassController mKeyguardBypassController;
-    private KeyguardHostViewController mKeyguardViewController;
-    private final ListenerSet<KeyguardResetCallback> mResetCallbacks = new ListenerSet<>();
-    private final Runnable mResetRunnable = ()-> {
-        if (mKeyguardViewController != null) {
-            mKeyguardViewController.resetSecurityContainer();
-            for (KeyguardResetCallback callback : mResetCallbacks) {
-                callback.onKeyguardReset();
-            }
-        }
-    };
-
-    private int mStatusBarHeight;
-    private float mExpansion = EXPANSION_HIDDEN;
-    private boolean mShowingSoon;
-    private int mBouncerPromptReason;
-    private boolean mIsAnimatingAway;
-    private boolean mIsScrimmed;
-    private boolean mInitialized;
-
-    private KeyguardBouncer(Context context, ViewMediatorCallback callback,
-            ViewGroup container,
-            DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector,
-            PrimaryBouncerExpansionCallback expansionCallback,
-            KeyguardStateController keyguardStateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardBypassController keyguardBypassController, @Main Handler handler,
-            KeyguardSecurityModel keyguardSecurityModel,
-            KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
-        mContext = context;
-        mCallback = callback;
-        mContainer = container;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mFalsingCollector = falsingCollector;
-        mDismissCallbackRegistry = dismissCallbackRegistry;
-        mHandler = handler;
-        mKeyguardStateController = keyguardStateController;
-        mKeyguardSecurityModel = keyguardSecurityModel;
-        mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory;
-        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
-        mKeyguardBypassController = keyguardBypassController;
-        mExpansionCallbacks.add(expansionCallback);
-    }
-
-    /**
-     * Get the KeyguardBouncer expansion
-     * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition.
-     */
-    public float getExpansion() {
-        return mExpansion;
-    }
-
-    /**
-     * Enable/disable only the back button
-     */
-    public void setBackButtonEnabled(boolean enabled) {
-        int vis = mContainer.getSystemUiVisibility();
-        if (enabled) {
-            vis &= ~View.STATUS_BAR_DISABLE_BACK;
-        } else {
-            vis |= View.STATUS_BAR_DISABLE_BACK;
-        }
-        mContainer.setSystemUiVisibility(vis);
-    }
-
-    public void show(boolean resetSecuritySelection) {
-        show(resetSecuritySelection, true /* scrimmed */);
-    }
-
-    /**
-     * Shows the bouncer.
-     *
-     * @param resetSecuritySelection Cleans keyguard view
-     * @param isScrimmed true when the bouncer show show scrimmed, false when the user will be
-     *                 dragging it and translation should be deferred.
-     */
-    public void show(boolean resetSecuritySelection, boolean isScrimmed) {
-        final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser();
-        if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
-            // In split system user mode, we never unlock system user.
-            return;
-        }
-
-        try {
-            Trace.beginSection("KeyguardBouncer#show");
-
-            ensureView();
-            mIsScrimmed = isScrimmed;
-
-            // On the keyguard, we want to show the bouncer when the user drags up, but it's
-            // not correct to end the falsing session. We still need to verify if those touches
-            // are valid.
-            // Later, at the end of the animation, when the bouncer is at the top of the screen,
-            // onFullyShown() will be called and FalsingManager will stop recording touches.
-            if (isScrimmed) {
-                setExpansion(EXPANSION_VISIBLE);
-            }
-
-            if (resetSecuritySelection) {
-                // showPrimarySecurityScreen() updates the current security method. This is needed
-                // in case we are already showing and the current security method changed.
-                showPrimarySecurityScreen();
-            }
-
-            if (mContainer.getVisibility() == View.VISIBLE || mShowingSoon) {
-                // Calls to reset must resume the ViewControllers when in fullscreen mode
-                if (needsFullscreenBouncer()) {
-                    mKeyguardViewController.onResume();
-                }
-                return;
-            }
-
-            final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
-            final boolean isSystemUser =
-                UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
-            final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
-
-            // If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern)
-            // is set, this will dismiss the whole Keyguard. Otherwise, show the bouncer.
-            if (allowDismissKeyguard && mKeyguardViewController.dismiss(activeUserId)) {
-                return;
-            }
-
-            // This condition may indicate an error on Android, so log it.
-            if (!allowDismissKeyguard) {
-                Log.w(TAG, "User can't dismiss keyguard: " + activeUserId + " != "
-                        + keyguardUserId);
-            }
-
-            mShowingSoon = true;
-
-            // Split up the work over multiple frames.
-            DejankUtils.removeCallbacks(mResetRunnable);
-            if (mKeyguardStateController.isFaceAuthEnabled()
-                    && !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
-                            KeyguardUpdateMonitor.getCurrentUser())
-                    && !needsFullscreenBouncer()
-                    && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
-                            BiometricSourceType.FACE)
-                    && !mKeyguardBypassController.getBypassEnabled()) {
-                mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
-            } else {
-                DejankUtils.postAfterTraversal(mShowRunnable);
-            }
-
-            mKeyguardStateController.notifyBouncerShowing(true /* showing */);
-            dispatchStartingToShow();
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    public boolean isScrimmed() {
-        return mIsScrimmed;
-    }
-
-    /**
-     * This method must be called at the end of the bouncer animation when
-     * the translation is performed manually by the user, otherwise FalsingManager
-     * will never be notified and its internal state will be out of sync.
-     */
-    private void onFullyShown() {
-        mFalsingCollector.onBouncerShown();
-        if (mKeyguardViewController == null) {
-            Log.e(TAG, "onFullyShown when view was null");
-        } else {
-            mKeyguardViewController.onResume();
-            mContainer.announceForAccessibility(
-                    mKeyguardViewController.getAccessibilityTitleForCurrentMode());
-        }
-    }
-
-    /**
-     * @see #onFullyShown()
-     */
-    private void onFullyHidden() {
-
-    }
-
-    private void setVisibility(@View.Visibility int visibility) {
-        mContainer.setVisibility(visibility);
-        if (mKeyguardViewController != null) {
-            mKeyguardViewController.onBouncerVisibilityChanged(visibility);
-        }
-        dispatchVisibilityChanged();
-    }
-
-    private final Runnable mShowRunnable = new Runnable() {
-        @Override
-        public void run() {
-            setVisibility(View.VISIBLE);
-            showPromptReason(mBouncerPromptReason);
-            final CharSequence customMessage = mCallback.consumeCustomMessage();
-            if (customMessage != null) {
-                mKeyguardViewController.showErrorMessage(customMessage);
-            }
-            mKeyguardViewController.appear(mStatusBarHeight);
-            mShowingSoon = false;
-            if (mExpansion == EXPANSION_VISIBLE) {
-                mKeyguardViewController.onResume();
-                mKeyguardViewController.resetSecurityContainer();
-                showPromptReason(mBouncerPromptReason);
-            }
-        }
-    };
-
-    /**
-     * Show a string explaining why the security view needs to be solved.
-     *
-     * @param reason a flag indicating which string should be shown, see
-     *               {@link KeyguardSecurityView#PROMPT_REASON_NONE}
-     *               and {@link KeyguardSecurityView#PROMPT_REASON_RESTART}
-     */
-    public void showPromptReason(int reason) {
-        if (mKeyguardViewController != null) {
-            mKeyguardViewController.showPromptReason(reason);
-        } else {
-            Log.w(TAG, "Trying to show prompt reason on empty bouncer");
-        }
-    }
-
-    public void showMessage(String message, ColorStateList colorState) {
-        if (mKeyguardViewController != null) {
-            mKeyguardViewController.showMessage(message, colorState);
-        } else {
-            Log.w(TAG, "Trying to show message on empty bouncer");
-        }
-    }
-
-    private void cancelShowRunnable() {
-        DejankUtils.removeCallbacks(mShowRunnable);
-        mHandler.removeCallbacks(mShowRunnable);
-        mShowingSoon = false;
-    }
-
-    public void showWithDismissAction(OnDismissAction r, Runnable cancelAction) {
-        ensureView();
-        setDismissAction(r, cancelAction);
-        show(false /* resetSecuritySelection */);
-    }
-
-    /**
-     * Set the actions to run when the keyguard is dismissed or when the dismiss is cancelled. Those
-     * actions will still be run even if this bouncer is not shown, for instance when authenticating
-     * with an alternate authenticator like the UDFPS.
-     */
-    public void setDismissAction(OnDismissAction r, Runnable cancelAction) {
-        mKeyguardViewController.setOnDismissAction(r, cancelAction);
-    }
-
-    public void hide(boolean destroyView) {
-        Trace.beginSection("KeyguardBouncer#hide");
-        if (isShowing()) {
-            SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
-                    SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN);
-            mDismissCallbackRegistry.notifyDismissCancelled();
-        }
-        mIsScrimmed = false;
-        mFalsingCollector.onBouncerHidden();
-        mKeyguardStateController.notifyBouncerShowing(false /* showing */);
-        cancelShowRunnable();
-        if (mKeyguardViewController != null) {
-            mKeyguardViewController.cancelDismissAction();
-            mKeyguardViewController.cleanUp();
-        }
-        mIsAnimatingAway = false;
-        setVisibility(View.INVISIBLE);
-        if (destroyView) {
-
-            // We have a ViewFlipper that unregisters a broadcast when being detached, which may
-            // be slow because of AM lock contention during unlocking. We can delay it a bit.
-            mHandler.postDelayed(mRemoveViewRunnable, 50);
-        }
-        Trace.endSection();
-    }
-
-    /**
-     * See {@link StatusBarKeyguardViewManager#startPreHideAnimation}.
-     */
-    public void startPreHideAnimation(Runnable runnable) {
-        mIsAnimatingAway = true;
-        if (mKeyguardViewController != null) {
-            mKeyguardViewController.startDisappearAnimation(runnable);
-        } else if (runnable != null) {
-            runnable.run();
-        }
-    }
-
-    /**
-     * Reset the state of the view.
-     */
-    public void reset() {
-        cancelShowRunnable();
-        inflateView();
-        mFalsingCollector.onBouncerHidden();
-    }
-
-    public void onScreenTurnedOff() {
-        if (mKeyguardViewController != null && mContainer.getVisibility() == View.VISIBLE) {
-            mKeyguardViewController.onPause();
-        }
-    }
-
-    public boolean isShowing() {
-        return (mShowingSoon || mContainer.getVisibility() == View.VISIBLE)
-                && mExpansion == EXPANSION_VISIBLE && !isAnimatingAway();
-    }
-
-    /**
-     * {@link #show(boolean)} was called but we're not showing yet, or being dragged.
-     */
-    public boolean inTransit() {
-        return mShowingSoon || mExpansion != EXPANSION_HIDDEN && mExpansion != EXPANSION_VISIBLE;
-    }
-
-    /**
-     * @return {@code true} when bouncer's pre-hide animation already started but isn't completely
-     *         hidden yet, {@code false} otherwise.
-     */
-    public boolean isAnimatingAway() {
-        return mIsAnimatingAway;
-    }
-
-    public void prepare() {
-        boolean wasInitialized = mInitialized;
-        ensureView();
-        if (wasInitialized) {
-            showPrimarySecurityScreen();
-        }
-        mBouncerPromptReason = mCallback.getBouncerPromptReason();
-    }
-
-    private void showPrimarySecurityScreen() {
-        mKeyguardViewController.showPrimarySecurityScreen();
-    }
-
-    /**
-     * Current notification panel expansion
-     * @param fraction 0 when notification panel is collapsed and 1 when expanded.
-     * @see StatusBarKeyguardViewManager#onPanelExpansionChanged
-     */
-    public void setExpansion(float fraction) {
-        float oldExpansion = mExpansion;
-        boolean expansionChanged = mExpansion != fraction;
-        mExpansion = fraction;
-        if (mKeyguardViewController != null && !mIsAnimatingAway) {
-            mKeyguardViewController.setExpansion(fraction);
-        }
-
-        if (fraction == EXPANSION_VISIBLE && oldExpansion != EXPANSION_VISIBLE) {
-            onFullyShown();
-            dispatchFullyShown();
-        } else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) {
-            DejankUtils.postAfterTraversal(mResetRunnable);
-            /*
-             * There are cases where #hide() was not invoked, such as when
-             * NotificationPanelViewController controls the hide animation. Make sure the state gets
-             * updated by calling #hide() directly.
-             */
-            hide(false /* destroyView */);
-            dispatchFullyHidden();
-        } else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
-            dispatchStartingToHide();
-            if (mKeyguardViewController != null) {
-                mKeyguardViewController.onStartingToHide();
-            }
-        }
-
-        if (expansionChanged) {
-            dispatchExpansionChanged();
-        }
-    }
-
-    public boolean willDismissWithAction() {
-        return mKeyguardViewController != null && mKeyguardViewController.hasDismissActions();
-    }
-
-    public int getTop() {
-        if (mKeyguardViewController == null) {
-            return 0;
-        }
-
-        return mKeyguardViewController.getTop();
-    }
-
-    protected void ensureView() {
-        // Removal of the view might be deferred to reduce unlock latency,
-        // in this case we need to force the removal, otherwise we'll
-        // end up in an unpredictable state.
-        boolean forceRemoval = mHandler.hasCallbacks(mRemoveViewRunnable);
-        if (!mInitialized || forceRemoval) {
-            inflateView();
-        }
-    }
-
-    protected void inflateView() {
-        removeView();
-        mHandler.removeCallbacks(mRemoveViewRunnable);
-
-        KeyguardBouncerComponent component = mKeyguardBouncerComponentFactory.create(mContainer);
-        mKeyguardViewController = component.getKeyguardHostViewController();
-        mKeyguardViewController.init();
-
-        mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
-        setVisibility(View.INVISIBLE);
-
-        final WindowInsets rootInsets = mContainer.getRootWindowInsets();
-        if (rootInsets != null) {
-            mContainer.dispatchApplyWindowInsets(rootInsets);
-        }
-        mInitialized = true;
-    }
-
-    protected void removeView() {
-        mContainer.removeAllViews();
-        mInitialized = false;
-    }
-
-    /**
-     * @return True if and only if the security method should be shown before showing the
-     * notifications on Keyguard, like SIM PIN/PUK.
-     */
-    public boolean needsFullscreenBouncer() {
-        SecurityMode mode = mKeyguardSecurityModel.getSecurityMode(
-                KeyguardUpdateMonitor.getCurrentUser());
-        return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
-    }
-
-    /**
-     * Like {@link #needsFullscreenBouncer}, but uses the currently visible security method, which
-     * makes this method much faster.
-     */
-    public boolean isFullscreenBouncer() {
-        if (mKeyguardViewController != null) {
-            SecurityMode mode = mKeyguardViewController.getCurrentSecurityMode();
-            return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
-        }
-        return false;
-    }
-
-    /**
-     * WARNING: This method might cause Binder calls.
-     */
-    public boolean isSecure() {
-        return mKeyguardSecurityModel.getSecurityMode(
-                KeyguardUpdateMonitor.getCurrentUser()) != SecurityMode.None;
-    }
-
-    public boolean shouldDismissOnMenuPressed() {
-        return mKeyguardViewController.shouldEnableMenuKey();
-    }
-
-    public boolean interceptMediaKey(KeyEvent event) {
-        ensureView();
-        return mKeyguardViewController.interceptMediaKey(event);
-    }
-
-    /**
-     * @return true if the pre IME back event should be handled
-     */
-    public boolean dispatchBackKeyEventPreIme() {
-        ensureView();
-        return mKeyguardViewController.dispatchBackKeyEventPreIme();
-    }
-
-    public void notifyKeyguardAuthenticated(boolean strongAuth) {
-        ensureView();
-        mKeyguardViewController.finish(strongAuth, KeyguardUpdateMonitor.getCurrentUser());
-    }
-
-    private void dispatchFullyShown() {
-        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
-            callback.onFullyShown();
-        }
-    }
-
-    private void dispatchStartingToHide() {
-        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
-            callback.onStartingToHide();
-        }
-    }
-
-    private void dispatchStartingToShow() {
-        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
-            callback.onStartingToShow();
-        }
-    }
-
-    private void dispatchFullyHidden() {
-        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
-            callback.onFullyHidden();
-        }
-    }
-
-    private void dispatchExpansionChanged() {
-        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
-            callback.onExpansionChanged(mExpansion);
-        }
-    }
-
-    private void dispatchVisibilityChanged() {
-        for (PrimaryBouncerExpansionCallback callback : mExpansionCallbacks) {
-            callback.onVisibilityChanged(mContainer.getVisibility() == View.VISIBLE);
-        }
-    }
-
-    /**
-     * Apply keyguard configuration from the currently active resources. This can be called when the
-     * device configuration changes, to re-apply some resources that are qualified on the device
-     * configuration.
-     */
-    public void updateResources() {
-        if (mKeyguardViewController != null) {
-            mKeyguardViewController.updateResources();
-        }
-    }
-
-    public void dump(PrintWriter pw) {
-        pw.println("KeyguardBouncer");
-        pw.println("  isShowing(): " + isShowing());
-        pw.println("  mStatusBarHeight: " + mStatusBarHeight);
-        pw.println("  mExpansion: " + mExpansion);
-        pw.println("  mKeyguardViewController; " + mKeyguardViewController);
-        pw.println("  mShowingSoon: " + mShowingSoon);
-        pw.println("  mBouncerPromptReason: " + mBouncerPromptReason);
-        pw.println("  mIsAnimatingAway: " + mIsAnimatingAway);
-        pw.println("  mInitialized: " + mInitialized);
-    }
-
-    /** Update keyguard position based on a tapped X coordinate. */
-    public void updateKeyguardPosition(float x) {
-        if (mKeyguardViewController != null) {
-            mKeyguardViewController.updateKeyguardPosition(x);
-        }
-    }
-
-    public void addKeyguardResetCallback(KeyguardResetCallback callback) {
-        mResetCallbacks.addIfAbsent(callback);
-    }
-
-    public void removeKeyguardResetCallback(KeyguardResetCallback callback) {
-        mResetCallbacks.remove(callback);
-    }
-
-    /**
-     * Adds a callback to listen to bouncer expansion updates.
-     */
-    public void addBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
-        if (!mExpansionCallbacks.contains(callback)) {
-            mExpansionCallbacks.add(callback);
-        }
-    }
-
-    /**
-     * Removes a previously added callback. If the callback was never added, this methood
-     * does nothing.
-     */
-    public void removeBouncerExpansionCallback(PrimaryBouncerExpansionCallback callback) {
-        mExpansionCallbacks.remove(callback);
-    }
-
-    /**
-     * Callback updated when the primary bouncer's show and hide states change.
-     */
-    public interface PrimaryBouncerExpansionCallback {
-        /**
-         * Invoked when the bouncer expansion reaches {@link KeyguardBouncer#EXPANSION_VISIBLE}.
-         * This is NOT called each time the bouncer is shown, but rather only when the fully
-         * shown amount has changed based on the panel expansion. The bouncer's visibility
-         * can still change when the expansion amount hasn't changed.
-         * See {@link KeyguardBouncer#isShowing()} for the checks for the bouncer showing state.
-         */
-        default void onFullyShown() {
-        }
-
-        /**
-         * Invoked when the bouncer is starting to transition to a hidden state.
-         */
-        default void onStartingToHide() {
-        }
-
-        /**
-         * Invoked when the bouncer is starting to transition to a visible state.
-         */
-        default void onStartingToShow() {
-        }
-
-        /**
-         * Invoked when the bouncer expansion reaches {@link KeyguardBouncer#EXPANSION_HIDDEN}.
-         */
-        default void onFullyHidden() {
-        }
-
-        /**
-         * From 0f {@link KeyguardBouncer#EXPANSION_VISIBLE} when fully visible
-         * to 1f {@link KeyguardBouncer#EXPANSION_HIDDEN} when fully hidden
-         */
-        default void onExpansionChanged(float bouncerHideAmount) {}
-
-        /**
-         * Invoked when visibility of KeyguardBouncer has changed.
-         * Note the bouncer expansion can be {@link KeyguardBouncer#EXPANSION_VISIBLE}, but the
-         * view's visibility can be {@link View.INVISIBLE}.
-         */
-        default void onVisibilityChanged(boolean isVisible) {}
-    }
-
-    public interface KeyguardResetCallback {
-        void onKeyguardReset();
-    }
-
-    /** Create a {@link KeyguardBouncer} once a container and bouncer callback are available. */
-    public static class Factory {
-        private final Context mContext;
-        private final ViewMediatorCallback mCallback;
-        private final DismissCallbackRegistry mDismissCallbackRegistry;
-        private final FalsingCollector mFalsingCollector;
-        private final KeyguardStateController mKeyguardStateController;
-        private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-        private final KeyguardBypassController mKeyguardBypassController;
-        private final Handler mHandler;
-        private final KeyguardSecurityModel mKeyguardSecurityModel;
-        private final KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
-
-        @Inject
-        public Factory(Context context, ViewMediatorCallback callback,
-                DismissCallbackRegistry dismissCallbackRegistry, FalsingCollector falsingCollector,
-                KeyguardStateController keyguardStateController,
-                KeyguardUpdateMonitor keyguardUpdateMonitor,
-                KeyguardBypassController keyguardBypassController, @Main Handler handler,
-                KeyguardSecurityModel keyguardSecurityModel,
-                KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) {
-            mContext = context;
-            mCallback = callback;
-            mDismissCallbackRegistry = dismissCallbackRegistry;
-            mFalsingCollector = falsingCollector;
-            mKeyguardStateController = keyguardStateController;
-            mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-            mKeyguardBypassController = keyguardBypassController;
-            mHandler = handler;
-            mKeyguardSecurityModel = keyguardSecurityModel;
-            mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory;
-        }
-
-        /**
-         * Construct a KeyguardBouncer that will exist in the given container.
-         */
-        public KeyguardBouncer create(ViewGroup container,
-                PrimaryBouncerExpansionCallback expansionCallback) {
-            return new KeyguardBouncer(mContext, mCallback, container,
-                    mDismissCallbackRegistry, mFalsingCollector, expansionCallback,
-                    mKeyguardStateController, mKeyguardUpdateMonitor,
-                    mKeyguardBypassController, mHandler, mKeyguardSecurityModel,
-                    mKeyguardBouncerComponentFactory);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 01af486..c163a89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -89,14 +89,19 @@
     private float mPanelExpansion;
 
     /**
-     * Burn-in prevention x translation.
+     * Max burn-in prevention x translation.
      */
-    private int mBurnInPreventionOffsetX;
+    private int mMaxBurnInPreventionOffsetX;
 
     /**
-     * Burn-in prevention y translation for clock layouts.
+     * Max burn-in prevention y translation for clock layouts.
      */
-    private int mBurnInPreventionOffsetYClock;
+    private int mMaxBurnInPreventionOffsetYClock;
+
+    /**
+     * Current burn-in prevention y translation.
+     */
+    private float mCurrentBurnInOffsetY;
 
     /**
      * Doze/AOD transition amount.
@@ -155,9 +160,9 @@
 
         mContainerTopPadding =
                 res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
-        mBurnInPreventionOffsetX = res.getDimensionPixelSize(
+        mMaxBurnInPreventionOffsetX = res.getDimensionPixelSize(
                 R.dimen.burn_in_prevention_offset_x);
-        mBurnInPreventionOffsetYClock = res.getDimensionPixelSize(
+        mMaxBurnInPreventionOffsetYClock = res.getDimensionPixelSize(
                 R.dimen.burn_in_prevention_offset_y_clock);
     }
 
@@ -215,7 +220,10 @@
         if (mBypassEnabled) {
             return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount);
         } else if (mIsSplitShade) {
-            return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight;
+            // mCurrentBurnInOffsetY is subtracted to make notifications not follow clock adjustment
+            // for burn-in. It can make pulsing notification go too high and it will get clipped
+            return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight
+                    - (int) mCurrentBurnInOffsetY;
         } else {
             return clockYPosition + mKeyguardStatusHeight;
         }
@@ -255,11 +263,11 @@
 
         // This will keep the clock at the top but out of the cutout area
         float shift = 0;
-        if (clockY - mBurnInPreventionOffsetYClock < mCutoutTopInset) {
-            shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYClock);
+        if (clockY - mMaxBurnInPreventionOffsetYClock < mCutoutTopInset) {
+            shift = mCutoutTopInset - (clockY - mMaxBurnInPreventionOffsetYClock);
         }
 
-        int burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; // requested offset
+        int burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock; // requested offset
         final boolean hasUdfps = mUdfpsTop > -1;
         if (hasUdfps && !mIsClockTopAligned) {
             // ensure clock doesn't overlap with the udfps icon
@@ -267,8 +275,8 @@
                 // sometimes the clock textView extends beyond udfps, so let's just use the
                 // space above the KeyguardStatusView/clock as our burn-in offset
                 burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2;
-                if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
-                    burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
+                if (mMaxBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+                    burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock;
                 }
                 shift = -burnInPreventionOffsetY;
             } else {
@@ -276,16 +284,18 @@
                 float lowerSpace = mUdfpsTop - mClockBottom;
                 // center the burn-in offset within the upper + lower space
                 burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2;
-                if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
-                    burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
+                if (mMaxBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+                    burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock;
                 }
                 shift = (lowerSpace - upperSpace) / 2;
             }
         }
 
+        float fullyDarkBurnInOffset = burnInPreventionOffsetY(burnInPreventionOffsetY);
         float clockYDark = clockY
-                + burnInPreventionOffsetY(burnInPreventionOffsetY)
+                + fullyDarkBurnInOffset
                 + shift;
+        mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount);
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
     }
 
@@ -325,7 +335,7 @@
     }
 
     private float burnInPreventionOffsetX() {
-        return getBurnInOffset(mBurnInPreventionOffsetX, true /* xAxis */);
+        return getBurnInOffset(mMaxBurnInPreventionOffsetX, true /* xAxis */);
     }
 
     public static class Result {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 4ad3199..cba0897 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -357,7 +357,7 @@
         mView.setOnApplyWindowInsetsListener(
                 (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider));
         mSecureSettings.registerContentObserverForUser(
-                Settings.Secure.getUriFor(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON),
+                Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
                 false,
                 mVolumeSettingObserver,
                 UserHandle.USER_ALL);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 4d14542..fe2a913 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
@@ -38,6 +37,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import java.io.PrintWriter;
@@ -94,7 +94,8 @@
             DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            DisplayTracker displayTracker) {
         mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone);
         mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone);
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
@@ -104,7 +105,7 @@
             mNavigationMode = mode;
         });
 
-        if (ctx.getDisplayId() == DEFAULT_DISPLAY) {
+        if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) {
             dumpManager.registerDumpable(getClass().getSimpleName(), this);
         }
     }
@@ -317,24 +318,27 @@
         private final BatteryController mBatteryController;
         private final NavigationModeController mNavModeController;
         private final DumpManager mDumpManager;
+        private final DisplayTracker mDisplayTracker;
 
         @Inject
         public Factory(
                 DarkIconDispatcher darkIconDispatcher,
                 BatteryController batteryController,
                 NavigationModeController navModeController,
-                DumpManager dumpManager) {
+                DumpManager dumpManager,
+                DisplayTracker displayTracker) {
 
             mDarkIconDispatcher = darkIconDispatcher;
             mBatteryController = batteryController;
             mNavModeController = navModeController;
             mDumpManager = dumpManager;
+            mDisplayTracker = displayTracker;
         }
 
         /** Create an {@link LightBarController} */
         public LightBarController create(Context context) {
             return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
-                    mNavModeController, mDumpManager);
+                    mNavModeController, mDumpManager, mDisplayTracker);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 48e58fc..6c532a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -35,7 +35,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings.Global;
 import android.service.notification.ZenModeConfig;
@@ -58,6 +57,7 @@
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
 import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.CastController;
@@ -136,6 +136,7 @@
     private final UserInfoController mUserInfoController;
     private final IActivityManager mIActivityManager;
     private final UserManager mUserManager;
+    private final UserTracker mUserTracker;
     private final DevicePolicyManager mDevicePolicyManager;
     private final StatusBarIconController mIconController;
     private final CommandQueue mCommandQueue;
@@ -176,7 +177,7 @@
             KeyguardStateController keyguardStateController,
             LocationController locationController,
             SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager,
-            AlarmManager alarmManager, UserManager userManager,
+            AlarmManager alarmManager, UserManager userManager, UserTracker userTracker,
             DevicePolicyManager devicePolicyManager, RecordingController recordingController,
             @Nullable TelecomManager telecomManager, @DisplayId int displayId,
             @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
@@ -196,6 +197,7 @@
         mUserInfoController = userInfoController;
         mIActivityManager = iActivityManager;
         mUserManager = userManager;
+        mUserTracker = userTracker;
         mDevicePolicyManager = devicePolicyManager;
         mRotationLockController = rotationLockController;
         mDataSaver = dataSaverController;
@@ -366,7 +368,7 @@
     }
 
     private void updateAlarm() {
-        final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+        final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
         final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
         int zen = mZenController.getZen();
         final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index f784723..80093a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -791,20 +791,28 @@
             if (!mScreenOffAnimationController.shouldExpandNotifications()
                     && !mAnimatingPanelExpansionOnUnlock
                     && !occluding) {
-                float behindFraction = getInterpolatedFraction();
-                behindFraction = (float) Math.pow(behindFraction, 0.8f);
                 if (mClipsQsScrim) {
+                    float behindFraction = getInterpolatedFraction();
+                    behindFraction = (float) Math.pow(behindFraction, 0.8f);
                     mBehindAlpha = mTransparentScrimBackground ? 0 : 1;
                     mNotificationsAlpha =
                             mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
                 } else {
-                    mBehindAlpha =
-                            mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
-                    // Delay fade-in of notification scrim a bit further, to coincide with the
-                    // view fade in. Otherwise the empty panel can be quite jarring.
-                    mNotificationsAlpha = mTransparentScrimBackground
-                            ? 0 : MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f,
-                            mPanelExpansionFraction);
+                    if (mTransparentScrimBackground) {
+                        mBehindAlpha = 0;
+                        mNotificationsAlpha = 0;
+                    } else {
+                        // Behind scrim will finish fading in at 30% expansion.
+                        float behindFraction = MathUtils
+                                .constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction);
+                        mBehindAlpha = behindFraction * mDefaultScrimAlpha;
+                        // Delay fade-in of notification scrim a bit further, to coincide with the
+                        // behind scrim finishing fading in.
+                        // Also to coincide with the view starting to fade in, otherwise the empty
+                        // panel can be quite jarring.
+                        mNotificationsAlpha = MathUtils
+                                .constrainedMap(0f, 1f, 0.3f, 0.75f, mPanelExpansionFraction);
+                    }
                 }
                 mBehindTint = mState.getBehindTint();
                 mInFrontAlpha = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 24ad55d..11863627 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -501,7 +501,7 @@
         @VisibleForTesting
         protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
             if (mStatusBarPipelineFlags.useNewWifiIcon()) {
-                throw new IllegalStateException("Attempting to add a mobile icon while the new "
+                throw new IllegalStateException("Attempting to add a wifi icon while the new "
                         + "icons are enabled is not supported");
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index aef25e3..fd46571 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -37,7 +37,8 @@
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
-import android.window.OnBackInvokedCallback;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
@@ -61,6 +62,7 @@
 import com.android.systemui.keyguard.data.BouncerView;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -77,7 +79,6 @@
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.unfold.FoldAodAnimationController;
@@ -128,7 +129,6 @@
     private final ConfigurationController mConfigurationController;
     private final NavigationModeController mNavigationModeController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
     private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
     private final DreamOverlayStateController mDreamOverlayStateController;
     @Nullable
@@ -198,11 +198,38 @@
             }
     };
 
-    private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
-        if (DEBUG) {
-            Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()");
+    private final OnBackAnimationCallback mOnBackInvokedCallback = new OnBackAnimationCallback() {
+        @Override
+        public void onBackInvoked() {
+            if (DEBUG) {
+                Log.d(TAG, "onBackInvokedCallback() called, invoking onBackPressed()");
+            }
+            onBackPressed();
+            if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
+                mPrimaryBouncerView.getDelegate().getBackCallback().onBackInvoked();
+            }
         }
-        onBackPressed();
+
+        @Override
+        public void onBackProgressed(BackEvent event) {
+            if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
+                mPrimaryBouncerView.getDelegate().getBackCallback().onBackProgressed(event);
+            }
+        }
+
+        @Override
+        public void onBackCancelled() {
+            if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
+                mPrimaryBouncerView.getDelegate().getBackCallback().onBackCancelled();
+            }
+        }
+
+        @Override
+        public void onBackStarted(BackEvent event) {
+            if (shouldPlayBackAnimation() && mPrimaryBouncerView.getDelegate() != null) {
+                mPrimaryBouncerView.getDelegate().getBackCallback().onBackStarted(event);
+            }
+        }
     };
     private boolean mIsBackCallbackRegistered = false;
 
@@ -228,7 +255,6 @@
 
     private View mNotificationContainer;
 
-    @Nullable protected KeyguardBouncer mPrimaryBouncer;
     protected boolean mRemoteInputActive;
     private boolean mGlobalActionsVisible = false;
     private boolean mLastGlobalActionsVisible = false;
@@ -253,9 +279,9 @@
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
-    private boolean mIsModernBouncerEnabled;
     private boolean mIsUnoccludeTransitionFlagEnabled;
     private boolean mIsModernAlternateBouncerEnabled;
+    private boolean mIsBackAnimationEnabled;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private Runnable mKeyguardGoneCancelAction;
@@ -300,7 +326,6 @@
             NotificationShadeWindowController notificationShadeWindowController,
             KeyguardStateController keyguardStateController,
             NotificationMediaManager notificationMediaManager,
-            KeyguardBouncer.Factory keyguardBouncerFactory,
             KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
             Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
             Lazy<ShadeController> shadeController,
@@ -323,7 +348,6 @@
         mKeyguardUpdateManager = keyguardUpdateMonitor;
         mStatusBarStateController = sysuiStatusBarStateController;
         mDockManager = dockManager;
-        mKeyguardBouncerFactory = keyguardBouncerFactory;
         mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
         mShadeController = shadeController;
         mLatencyTracker = latencyTracker;
@@ -333,10 +357,11 @@
         mPrimaryBouncerView = primaryBouncerView;
         mFoldAodAnimationController = sysUIUnfoldComponent
                 .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
-        mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
         mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
         mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER);
         mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mIsBackAnimationEnabled =
+                featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
     }
 
     @Override
@@ -350,11 +375,7 @@
         mBiometricUnlockController = biometricUnlockController;
 
         ViewGroup container = mCentralSurfaces.getBouncerContainer();
-        if (mIsModernBouncerEnabled) {
-            mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
-        } else {
-            mPrimaryBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
-        }
+        mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
         mNotificationPanelViewController = notificationPanelViewController;
         if (shadeExpansionStateManager != null) {
             shadeExpansionStateManager.addExpansionListener(this);
@@ -472,6 +493,11 @@
         }
     }
 
+    private boolean shouldPlayBackAnimation() {
+        // Suppress back animation when bouncer shouldn't be dismissed on back invocation.
+        return !needsFullscreenBouncer() && mIsBackAnimationEnabled;
+    }
+
     @Override
     public void onDensityOrFontScaleChanged() {
         hideBouncer(true /* destroyView */);
@@ -516,11 +542,7 @@
          * show if any subsequent events are to be handled.
          */
         if (beginShowingBouncer(event)) {
-            if (mPrimaryBouncer != null) {
-                mPrimaryBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
-            } else {
-                mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
-            }
+            mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
         }
 
         if (!primaryBouncerIsOrWillBeShowing()) {
@@ -528,17 +550,9 @@
         }
 
         if (mKeyguardStateController.isShowing()) {
-            if (mPrimaryBouncer != null) {
-                mPrimaryBouncer.setExpansion(fraction);
-            } else {
-                mPrimaryBouncerInteractor.setPanelExpansion(fraction);
-            }
+            mPrimaryBouncerInteractor.setPanelExpansion(fraction);
         } else {
-            if (mPrimaryBouncer != null) {
-                mPrimaryBouncer.setExpansion(EXPANSION_HIDDEN);
-            } else {
-                mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN);
-            }
+            mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN);
         }
     }
 
@@ -568,24 +582,17 @@
 
     /**
      * Shows the notification keyguard or the bouncer depending on
-     * {@link KeyguardBouncer#needsFullscreenBouncer()}.
+     * {@link #needsFullscreenBouncer()}.
      */
     protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
         if (needsFullscreenBouncer() && !mDozing) {
             // The keyguard might be showing (already). So we need to hide it.
             mCentralSurfaces.hideKeyguard();
-            if (mPrimaryBouncer != null) {
-                mPrimaryBouncer.show(true /* resetSecuritySelection */);
-            } else {
-                mPrimaryBouncerInteractor.show(true);
-            }
+            mPrimaryBouncerInteractor.show(true);
         } else {
             mCentralSurfaces.showKeyguard();
             if (hideBouncerWhenShowing) {
                 hideBouncer(false /* destroyView */);
-                if (mPrimaryBouncer != null) {
-                    mPrimaryBouncer.prepare();
-                }
             }
         }
         updateStates();
@@ -612,11 +619,7 @@
      */
     @VisibleForTesting
     void hideBouncer(boolean destroyView) {
-        if (mPrimaryBouncer != null) {
-            mPrimaryBouncer.hide(destroyView);
-        } else {
-            mPrimaryBouncerInteractor.hide();
-        }
+        mPrimaryBouncerInteractor.hide();
         if (mKeyguardStateController.isShowing()) {
             // If we were showing the bouncer and then aborting, we need to also clear out any
             // potential actions unless we actually unlocked.
@@ -635,11 +638,7 @@
         hideAlternateBouncer(false);
 
         if (mKeyguardStateController.isShowing()  && !isBouncerShowing()) {
-            if (mPrimaryBouncer != null) {
-                mPrimaryBouncer.show(false /* resetSecuritySelection */, scrimmed);
-            } else {
-                mPrimaryBouncerInteractor.show(scrimmed);
-            }
+            mPrimaryBouncerInteractor.show(scrimmed);
         }
         updateStates();
     }
@@ -672,13 +671,8 @@
                 // instead of the bouncer.
                 if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) {
                     if (!afterKeyguardGone) {
-                        if (mPrimaryBouncer != null) {
-                            mPrimaryBouncer.setDismissAction(mAfterKeyguardGoneAction,
-                                    mKeyguardGoneCancelAction);
-                        } else {
-                            mPrimaryBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
-                                    mKeyguardGoneCancelAction);
-                        }
+                        mPrimaryBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
+                                mKeyguardGoneCancelAction);
                         mAfterKeyguardGoneAction = null;
                         mKeyguardGoneCancelAction = null;
                     }
@@ -690,22 +684,13 @@
                 if (afterKeyguardGone) {
                     // we'll handle the dismiss action after keyguard is gone, so just show the
                     // bouncer
-                    if (mPrimaryBouncer != null) {
-                        mPrimaryBouncer.show(false /* resetSecuritySelection */);
-                    } else {
-                        mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
-                    }
+                    mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
                 } else {
                     // after authentication success, run dismiss action with the option to defer
                     // hiding the keyguard based on the return value of the OnDismissAction
-                    if (mPrimaryBouncer != null) {
-                        mPrimaryBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
-                                mKeyguardGoneCancelAction);
-                    } else {
-                        mPrimaryBouncerInteractor.setDismissAction(
-                                mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
-                        mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
-                    }
+                    mPrimaryBouncerInteractor.setDismissAction(
+                            mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
+                    mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
                     // bouncer will handle the dismiss action, so we no longer need to track it here
                     mAfterKeyguardGoneAction = null;
                     mKeyguardGoneCancelAction = null;
@@ -805,11 +790,7 @@
 
     @Override
     public void onFinishedGoingToSleep() {
-        if (mPrimaryBouncer != null) {
-            mPrimaryBouncer.onScreenTurnedOff();
-        } else {
-            mPrimaryBouncerInteractor.onScreenTurnedOff();
-        }
+        mPrimaryBouncerInteractor.onScreenTurnedOff();
     }
 
     @Override
@@ -903,11 +884,7 @@
     @Override
     public void startPreHideAnimation(Runnable finishRunnable) {
         if (primaryBouncerIsShowing()) {
-            if (mPrimaryBouncer != null) {
-                mPrimaryBouncer.startPreHideAnimation(finishRunnable);
-            } else {
-                mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable);
-            }
+            mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable);
             mNotificationPanelViewController.startBouncerPreHideAnimation();
 
             // We update the state (which will show the keyguard) only if an animation will run on
@@ -1015,17 +992,7 @@
     }
 
     public void onThemeChanged() {
-        if (mIsModernBouncerEnabled) {
-            updateResources();
-            return;
-        }
-        boolean wasShowing = primaryBouncerIsShowing();
-        boolean wasScrimmed = primaryBouncerIsScrimmed();
-
-        hideBouncer(true /* destroyView */);
-        mPrimaryBouncer.prepare();
-
-        if (wasShowing) showPrimaryBouncer(wasScrimmed);
+        updateResources();
     }
 
     public void onKeyguardFadedAway() {
@@ -1070,10 +1037,6 @@
      * WARNING: This method might cause Binder calls.
      */
     public boolean isSecure() {
-        if (mPrimaryBouncer != null) {
-            return mPrimaryBouncer.isSecure();
-        }
-
         return mKeyguardSecurityModel.getSecurityMode(
                 KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
     }
@@ -1111,7 +1074,7 @@
             if (hideImmediately) {
                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
             } else {
-                mNotificationPanelViewController.expandWithoutQs();
+                mNotificationPanelViewController.expandShadeToNotifications();
             }
         }
         return;
@@ -1128,10 +1091,8 @@
     }
 
     public boolean isFullscreenBouncer() {
-        if (mPrimaryBouncerView.getDelegate() != null) {
-            return mPrimaryBouncerView.getDelegate().isFullScreenBouncer();
-        }
-        return mPrimaryBouncer != null && mPrimaryBouncer.isFullscreenBouncer();
+        return mPrimaryBouncerView.getDelegate() != null
+                && mPrimaryBouncerView.getDelegate().isFullScreenBouncer();
     }
 
     /**
@@ -1187,17 +1148,9 @@
                 != (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
                 || mFirstUpdate) {
             if (primaryBouncerDismissible || !showing || remoteInputActive) {
-                if (mPrimaryBouncer != null) {
-                    mPrimaryBouncer.setBackButtonEnabled(true);
-                } else {
-                    mPrimaryBouncerInteractor.setBackButtonEnabled(true);
-                }
+                mPrimaryBouncerInteractor.setBackButtonEnabled(true);
             } else {
-                if (mPrimaryBouncer != null) {
-                    mPrimaryBouncer.setBackButtonEnabled(false);
-                } else {
-                    mPrimaryBouncerInteractor.setBackButtonEnabled(false);
-                }
+                mPrimaryBouncerInteractor.setBackButtonEnabled(false);
             }
         }
 
@@ -1291,27 +1244,21 @@
     }
 
     public boolean shouldDismissOnMenuPressed() {
-        if (mPrimaryBouncerView.getDelegate() != null) {
-            return mPrimaryBouncerView.getDelegate().shouldDismissOnMenuPressed();
-        }
-        return mPrimaryBouncer != null && mPrimaryBouncer.shouldDismissOnMenuPressed();
+        return mPrimaryBouncerView.getDelegate() != null
+                && mPrimaryBouncerView.getDelegate().shouldDismissOnMenuPressed();
     }
 
     public boolean interceptMediaKey(KeyEvent event) {
-        if (mPrimaryBouncerView.getDelegate() != null) {
-            return mPrimaryBouncerView.getDelegate().interceptMediaKey(event);
-        }
-        return mPrimaryBouncer != null && mPrimaryBouncer.interceptMediaKey(event);
+        return mPrimaryBouncerView.getDelegate() != null
+                && mPrimaryBouncerView.getDelegate().interceptMediaKey(event);
     }
 
     /**
      * @return true if the pre IME back event should be handled
      */
     public boolean dispatchBackKeyEventPreIme() {
-        if (mPrimaryBouncerView.getDelegate() != null) {
-            return mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme();
-        }
-        return mPrimaryBouncer != null && mPrimaryBouncer.dispatchBackKeyEventPreIme();
+        return mPrimaryBouncerView.getDelegate() != null
+                && mPrimaryBouncerView.getDelegate().dispatchBackKeyEventPreIme();
     }
 
     public void readyForKeyguardDone() {
@@ -1357,11 +1304,7 @@
      * fingerprint.
      */
     public void notifyKeyguardAuthenticated(boolean strongAuth) {
-        if (mPrimaryBouncer != null) {
-            mPrimaryBouncer.notifyKeyguardAuthenticated(strongAuth);
-        } else {
-            mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
-        }
+        mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
 
         if (mAlternateBouncerInteractor.isVisibleState()) {
             hideAlternateBouncer(false);
@@ -1376,11 +1319,7 @@
                 mKeyguardMessageAreaController.setMessage(message);
             }
         } else {
-            if (mPrimaryBouncer != null) {
-                mPrimaryBouncer.showMessage(message, colorState);
-            } else {
-                mPrimaryBouncerInteractor.showMessage(message, colorState);
-            }
+            mPrimaryBouncerInteractor.showMessage(message, colorState);
         }
     }
 
@@ -1436,11 +1375,7 @@
      * configuration.
      */
     public void updateResources() {
-        if (mPrimaryBouncer != null) {
-            mPrimaryBouncer.updateResources();
-        } else {
-            mPrimaryBouncerInteractor.updateResources();
-        }
+        mPrimaryBouncerInteractor.updateResources();
     }
 
     public void dump(PrintWriter pw) {
@@ -1458,11 +1393,6 @@
             pw.println("      " + callback);
         }
 
-        if (mPrimaryBouncer != null) {
-            pw.println("PrimaryBouncer:");
-            mPrimaryBouncer.dump(pw);
-        }
-
         if (mOccludingAppBiometricUI != null) {
             pw.println("mOccludingAppBiometricUI:");
             mOccludingAppBiometricUI.dump(pw);
@@ -1512,11 +1442,6 @@
         }
     }
 
-    @Nullable
-    public KeyguardBouncer getPrimaryBouncer() {
-        return mPrimaryBouncer;
-    }
-
     /**
      * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently
      * showing.
@@ -1535,11 +1460,7 @@
 
     /** Update keyguard position based on a tapped X coordinate. */
     public void updateKeyguardPosition(float x) {
-        if (mPrimaryBouncer != null) {
-            mPrimaryBouncer.updateKeyguardPosition(x);
-        } else {
-            mPrimaryBouncerInteractor.setKeyguardPosition(x);
-        }
+        mPrimaryBouncerInteractor.setKeyguardPosition(x);
     }
 
     private static class DismissWithActionRequest {
@@ -1579,56 +1500,35 @@
      * Returns if bouncer expansion is between 0 and 1 non-inclusive.
      */
     public boolean isPrimaryBouncerInTransit() {
-        if (mPrimaryBouncer != null) {
-            return mPrimaryBouncer.inTransit();
-        } else {
-            return mPrimaryBouncerInteractor.isInTransit();
-        }
+        return mPrimaryBouncerInteractor.isInTransit();
     }
 
     /**
      * Returns if bouncer is showing
      */
     public boolean primaryBouncerIsShowing() {
-        if (mPrimaryBouncer != null) {
-            return mPrimaryBouncer.isShowing();
-        } else {
-            return mPrimaryBouncerInteractor.isFullyShowing();
-        }
+        return mPrimaryBouncerInteractor.isFullyShowing();
     }
 
     /**
      * Returns if bouncer is scrimmed
      */
     public boolean primaryBouncerIsScrimmed() {
-        if (mPrimaryBouncer != null) {
-            return mPrimaryBouncer.isScrimmed();
-        } else {
-            return mPrimaryBouncerInteractor.isScrimmed();
-        }
+        return mPrimaryBouncerInteractor.isScrimmed();
     }
 
     /**
      * Returns if bouncer is animating away
      */
     public boolean bouncerIsAnimatingAway() {
-        if (mPrimaryBouncer != null) {
-            return mPrimaryBouncer.isAnimatingAway();
-        } else {
-            return mPrimaryBouncerInteractor.isAnimatingAway();
-        }
-
+        return mPrimaryBouncerInteractor.isAnimatingAway();
     }
 
     /**
      * Returns if bouncer will dismiss with action
      */
     public boolean primaryBouncerWillDismissWithAction() {
-        if (mPrimaryBouncer != null) {
-            return mPrimaryBouncer.willDismissWithAction();
-        } else {
-            return mPrimaryBouncerInteractor.willDismissWithAction();
-        }
+        return mPrimaryBouncerInteractor.willDismissWithAction();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index be6e0cc..078a00d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -53,6 +53,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationClickNotifier;
@@ -118,6 +119,7 @@
     private final NotificationPanelViewController mNotificationPanel;
     private final ActivityLaunchAnimator mActivityLaunchAnimator;
     private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
+    private final UserTracker mUserTracker;
     private final OnUserInteractionCallback mOnUserInteractionCallback;
 
     private boolean mIsCollapsingToShowActivityOverLockscreen;
@@ -153,7 +155,8 @@
             ActivityLaunchAnimator activityLaunchAnimator,
             NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
             LaunchFullScreenIntentProvider launchFullScreenIntentProvider,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            UserTracker userTracker) {
         mContext = context;
         mMainThreadHandler = mainThreadHandler;
         mUiBgExecutor = uiBgExecutor;
@@ -184,6 +187,7 @@
         mNotificationPanel = panel;
         mActivityLaunchAnimator = activityLaunchAnimator;
         mNotificationAnimationProvider = notificationAnimationProvider;
+        mUserTracker = userTracker;
 
         launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
     }
@@ -518,7 +522,7 @@
                             intent.getPackage(),
                             (adapter) -> tsb.startActivities(
                                     getActivityOptions(mCentralSurfaces.getDisplayId(), adapter),
-                                    UserHandle.CURRENT));
+                                    mUserTracker.getUserHandle()));
                 });
                 return true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index ae48c2d3..50cce45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -17,5 +17,5 @@
 
 public interface StatusBarWindowCallback {
     void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing,
-            boolean isDozing, boolean panelExpanded);
+            boolean isDozing, boolean panelExpanded, boolean isDreaming);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 206c0aa..c1c6c88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.ViewStub;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.LockIconView;
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
@@ -46,7 +47,6 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.OperatorNameViewController;
-import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.settings.SecureSettings;
@@ -293,7 +294,6 @@
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             KeyguardStateController keyguardStateController,
             NotificationPanelViewController notificationPanelViewController,
-            NetworkController networkController,
             StatusBarStateController statusBarStateController,
             CommandQueue commandQueue,
             CarrierConfigTracker carrierConfigTracker,
@@ -301,7 +301,9 @@
             OperatorNameViewController.Factory operatorNameViewControllerFactory,
             SecureSettings secureSettings,
             @Main Executor mainExecutor,
-            DumpManager dumpManager
+            DumpManager dumpManager,
+            StatusBarWindowStateController statusBarWindowStateController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor
     ) {
         return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
                 ongoingCallController,
@@ -315,7 +317,6 @@
                 statusBarHideIconsForBouncerManager,
                 keyguardStateController,
                 notificationPanelViewController,
-                networkController,
                 statusBarStateController,
                 commandQueue,
                 carrierConfigTracker,
@@ -323,7 +324,9 @@
                 operatorNameViewControllerFactory,
                 secureSettings,
                 mainExecutor,
-                dumpManager);
+                dumpManager,
+                statusBarWindowStateController,
+                keyguardUpdateMonitor);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 9f3fd72..00fd4ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -24,7 +24,6 @@
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
 
 import android.animation.Animator;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.Fragment;
@@ -45,6 +44,7 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
@@ -59,9 +59,6 @@
 import com.android.systemui.statusbar.OperatorNameView;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.connectivity.IconState;
-import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.connectivity.SignalCallback;
 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -75,8 +72,9 @@
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
-import com.android.systemui.statusbar.policy.EncryptionHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener;
 import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
@@ -110,7 +108,6 @@
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationPanelViewController mNotificationPanelViewController;
-    private final NetworkController mNetworkController;
     private LinearLayout mEndSideContent;
     private View mClockView;
     private View mOngoingCallChip;
@@ -135,17 +132,12 @@
     private final SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
     private final DumpManager mDumpManager;
+    private final StatusBarWindowStateController mStatusBarWindowStateController;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     private List<String> mBlockedIcons = new ArrayList<>();
     private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
 
-    private SignalCallback mSignalCallback = new SignalCallback() {
-        @Override
-        public void setIsAirplaneMode(@NonNull IconState icon) {
-            mCommandQueue.recomputeDisableFlags(getContext().getDisplayId(), true /* animate */);
-        }
-    };
-
     private final OngoingCallListener mOngoingCallListener = new OngoingCallListener() {
         @Override
         public void onOngoingCallStateChanged(boolean animate) {
@@ -177,6 +169,22 @@
                 }
             };
 
+    /**
+     * Whether we've launched the secure camera over the lockscreen, but haven't yet received a
+     * status bar window state change afterward.
+     *
+     * We wait for this state change (which will tell us whether to show/hide the status bar icons)
+     * so that there is no flickering/jump cutting during the camera launch.
+     */
+    private boolean mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
+    /**
+     * Listener that updates {@link #mWaitingForWindowStateChangeAfterCameraLaunch} when it receives
+     * a new status bar window state.
+     */
+    private final StatusBarWindowStateListener mStatusBarWindowStateListener = state ->
+            mWaitingForWindowStateChangeAfterCameraLaunch = false;
+
     @SuppressLint("ValidFragment")
     public CollapsedStatusBarFragment(
             StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
@@ -191,7 +199,6 @@
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             KeyguardStateController keyguardStateController,
             NotificationPanelViewController notificationPanelViewController,
-            NetworkController networkController,
             StatusBarStateController statusBarStateController,
             CommandQueue commandQueue,
             CarrierConfigTracker carrierConfigTracker,
@@ -199,7 +206,9 @@
             OperatorNameViewController.Factory operatorNameViewControllerFactory,
             SecureSettings secureSettings,
             @Main Executor mainExecutor,
-            DumpManager dumpManager
+            DumpManager dumpManager,
+            StatusBarWindowStateController statusBarWindowStateController,
+            KeyguardUpdateMonitor keyguardUpdateMonitor
     ) {
         mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
         mOngoingCallController = ongoingCallController;
@@ -213,7 +222,6 @@
         mDarkIconManagerFactory = darkIconManagerFactory;
         mKeyguardStateController = keyguardStateController;
         mNotificationPanelViewController = notificationPanelViewController;
-        mNetworkController = networkController;
         mStatusBarStateController = statusBarStateController;
         mCommandQueue = commandQueue;
         mCarrierConfigTracker = carrierConfigTracker;
@@ -222,6 +230,20 @@
         mSecureSettings = secureSettings;
         mMainExecutor = mainExecutor;
         mDumpManager = dumpManager;
+        mStatusBarWindowStateController = statusBarWindowStateController;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
     }
 
     @Override
@@ -261,7 +283,6 @@
         mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
         showEndSideContent(false);
         showClock(false);
-        initEmergencyCryptkeeperText();
         initOperatorName();
         initNotificationIconArea();
         mSystemEventAnimator =
@@ -270,6 +291,11 @@
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
     }
 
+    @Override
+    public void onCameraLaunchGestureDetected(int source) {
+        mWaitingForWindowStateChangeAfterCameraLaunch = true;
+    }
+
     @VisibleForTesting
     void updateBlockedIcons() {
         mBlockedIcons.clear();
@@ -320,7 +346,7 @@
         mAnimationScheduler.addCallback(this);
 
         mSecureSettings.registerContentObserverForUser(
-                Settings.Secure.getUriFor(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON),
+                Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
                 false,
                 mVolumeSettingObserver,
                 UserHandle.USER_ALL);
@@ -340,9 +366,6 @@
     public void onDestroyView() {
         super.onDestroyView();
         mStatusBarIconController.removeIconGroup(mDarkIconManager);
-        if (mNetworkController.hasEmergencyCryptKeeperText()) {
-            mNetworkController.removeCallback(mSignalCallback);
-        }
         mCarrierConfigTracker.removeCallback(mCarrierConfigCallback);
         mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener);
 
@@ -444,15 +467,6 @@
             state |= DISABLE_CLOCK;
         }
 
-        if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
-            if (mNetworkController.hasEmergencyCryptKeeperText()) {
-                state |= DISABLE_NOTIFICATION_ICONS;
-            }
-            if (!mNetworkController.isRadioOn()) {
-                state |= DISABLE_SYSTEM_INFO;
-            }
-        }
-
         if (mOngoingCallController.hasOngoingCall()) {
             state &= ~DISABLE_ONGOING_CALL_CHIP;
         } else {
@@ -494,6 +508,27 @@
                 && mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) {
             return true;
         }
+
+        // When launching the camera over the lockscreen, the icons become visible momentarily
+        // before animating out, since we're not yet aware that the launching camera activity is
+        // fullscreen. Even once the activity finishes launching, it takes a short time before WM
+        // decides that the top app wants to hide the icons and tells us to hide them. To ensure
+        // that this high-visibility animation is smooth, keep the icons hidden during a camera
+        // launch until we receive a window state change which indicates that the activity is done
+        // launching and WM has decided to show/hide the icons. For extra safety (to ensure the
+        // icons don't remain hidden somehow) we double check that the camera is still showing, the
+        // status bar window isn't hidden, and we're still occluded as well, though these checks
+        // are typically unnecessary.
+        final boolean hideIconsForSecureCamera =
+                (mWaitingForWindowStateChangeAfterCameraLaunch ||
+                        !mStatusBarWindowStateController.windowIsShowing()) &&
+                        mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard() &&
+                        mKeyguardStateController.isOccluded();
+
+        if (hideIconsForSecureCamera) {
+            return true;
+        }
+
         return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
     }
 
@@ -620,19 +655,6 @@
         }
     }
 
-    private void initEmergencyCryptkeeperText() {
-        View emergencyViewStub = mStatusBar.findViewById(R.id.emergency_cryptkeeper_text);
-        if (mNetworkController.hasEmergencyCryptKeeperText()) {
-            if (emergencyViewStub != null) {
-                ((ViewStub) emergencyViewStub).inflate();
-            }
-            mNetworkController.addCallback(mSignalCallback);
-        } else if (emergencyViewStub != null) {
-            ViewGroup parent = (ViewGroup) emergencyViewStub.getParent();
-            parent.removeView(emergencyViewStub);
-        }
-    }
-
     private void initOperatorName() {
         int subId = SubscriptionManager.getDefaultDataSubscriptionId();
         if (mCarrierConfigTracker.getShowOperatorNameInStatusBarConfig(subId)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 2c8677d..2d80edb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -19,14 +19,14 @@
 import android.content.Context
 import android.util.AttributeSet
 import android.widget.ImageView
-import android.widget.LinearLayout
 import android.widget.TextView
 import com.android.systemui.R
+import com.android.systemui.common.ui.view.LaunchableLinearLayout
 
 class StatusBarUserSwitcherContainer(
     context: Context?,
     attrs: AttributeSet?
-) : LinearLayout(context, attrs) {
+) : LaunchableLinearLayout(context, attrs) {
     lateinit var text: TextView
         private set
     lateinit var avatar: ImageView
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index 5479b92..85729c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -20,16 +20,21 @@
 import android.telephony.TelephonyManager.DATA_CONNECTING
 import android.telephony.TelephonyManager.DATA_DISCONNECTED
 import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_HANDOVER_IN_PROGRESS
+import android.telephony.TelephonyManager.DATA_SUSPENDED
 import android.telephony.TelephonyManager.DATA_UNKNOWN
 import android.telephony.TelephonyManager.DataState
 
 /** Internal enum representation of the telephony data connection states */
-enum class DataConnectionState(@DataState val dataState: Int) {
-    Connected(DATA_CONNECTED),
-    Connecting(DATA_CONNECTING),
-    Disconnected(DATA_DISCONNECTED),
-    Disconnecting(DATA_DISCONNECTING),
-    Unknown(DATA_UNKNOWN),
+enum class DataConnectionState {
+    Connected,
+    Connecting,
+    Disconnected,
+    Disconnecting,
+    Suspended,
+    HandoverInProgress,
+    Unknown,
+    Invalid,
 }
 
 fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
@@ -38,6 +43,8 @@
         DATA_CONNECTING -> DataConnectionState.Connecting
         DATA_DISCONNECTED -> DataConnectionState.Disconnected
         DATA_DISCONNECTING -> DataConnectionState.Disconnecting
+        DATA_SUSPENDED -> DataConnectionState.Suspended
+        DATA_HANDOVER_IN_PROGRESS -> DataConnectionState.HandoverInProgress
         DATA_UNKNOWN -> DataConnectionState.Unknown
-        else -> throw IllegalArgumentException("unknown data state received $this")
+        else -> DataConnectionState.Invalid
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 012b9ec..fdf5ffd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
@@ -94,7 +94,7 @@
 ) : Diffable<MobileConnectionModel> {
     override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
         if (prevVal.dataConnectionState != dataConnectionState) {
-            row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+            row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
         }
 
         if (prevVal.isEmergencyOnly != isEmergencyOnly) {
@@ -139,7 +139,7 @@
     }
 
     override fun logFull(row: TableRowLogger) {
-        row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+        row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
         row.logChange(COL_EMERGENCY, isEmergencyOnly)
         row.logChange(COL_ROAMING, isRoaming)
         row.logChange(COL_OPERATOR, operatorAlphaShort)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 97b4c2c..e0d156a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -18,6 +18,7 @@
 
 import android.provider.Settings
 import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.MobileMappings.Config
@@ -37,6 +38,15 @@
     /** Observable for the subscriptionId of the current mobile data connection */
     val activeMobileDataSubscriptionId: StateFlow<Int>
 
+    /**
+     * Observable event for when the active data sim switches but the group stays the same. E.g.,
+     * CBRS switching would trigger this
+     */
+    val activeSubChangedInGroupEvent: Flow<Unit>
+
+    /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
+    val defaultDataSubId: StateFlow<Int>
+
     /** The current connectivity status for the default mobile network connection */
     val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 0c8593d6..b939856 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -124,6 +124,9 @@
                 realRepository.activeMobileDataSubscriptionId.value
             )
 
+    override val activeSubChangedInGroupEvent: Flow<Unit> =
+        activeRepo.flatMapLatest { it.activeSubChangedInGroupEvent }
+
     override val defaultDataSubRatConfig: StateFlow<MobileMappings.Config> =
         activeRepo
             .flatMapLatest { it.defaultDataSubRatConfig }
@@ -139,6 +142,11 @@
     override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
         activeRepo.flatMapLatest { it.defaultMobileIconGroup }
 
+    override val defaultDataSubId: StateFlow<Int> =
+        activeRepo
+            .flatMapLatest { it.defaultDataSubId }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
+
     override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
         activeRepo
             .flatMapLatest { it.defaultMobileNetworkConnectivity }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 22aca0a..1088345 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -48,6 +48,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
@@ -120,6 +121,9 @@
                 subscriptions.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
             )
 
+    // TODO(b/261029387): consider adding a demo command for this
+    override val activeSubChangedInGroupEvent: Flow<Unit> = flowOf()
+
     /** Demo mode doesn't currently support modifications to the mobile mappings */
     override val defaultDataSubRatConfig =
         MutableStateFlow(MobileMappings.Config.readConfig(context))
@@ -148,8 +152,12 @@
 
     private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key }
 
+    // TODO(b/261029387): add a command for this value
+    override val defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+
     // TODO(b/261029387): not yet supported
-    override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
+    override val defaultMobileNetworkConnectivity =
+        MutableStateFlow(MobileConnectivityModel(isConnected = true, isValidated = true))
 
     override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
         val current = connectionRepoCache[subId]?.repo
@@ -229,6 +237,9 @@
         val connection = getRepoForSubId(subId)
         connectionRepoCache[subId]?.lastMobileState = state
 
+        // TODO(b/261029387): until we have a command, use the most recent subId
+        defaultDataSubId.value = subId
+
         // This is always true here, because we split out disabled states at the data-source level
         connection.dataEnabled.value = true
         connection.networkName.value = NetworkNameModel.Derived(state.name)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 4472e09..39ad31f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -35,6 +35,7 @@
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
 import android.telephony.TelephonyManager
 import androidx.annotation.VisibleForTesting
+import com.android.internal.telephony.PhoneConstants
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.MobileMappings.Config
 import com.android.systemui.R
@@ -52,6 +53,7 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -60,11 +62,14 @@
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
@@ -159,10 +164,24 @@
             .logInputChange(logger, "onActiveDataSubscriptionIdChanged")
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
 
-    private val defaultDataSubIdChangedEvent =
+    private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
+        MutableSharedFlow(extraBufferCapacity = 1)
+
+    override val defaultDataSubId: StateFlow<Int> =
         broadcastDispatcher
-            .broadcastFlow(IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED))
+            .broadcastFlow(
+                IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+            ) { intent, _ ->
+                intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+            }
+            .distinctUntilChanged()
             .logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
+            .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                SubscriptionManager.getDefaultDataSubscriptionId()
+            )
 
     private val carrierConfigChangedEvent =
         broadcastDispatcher
@@ -170,7 +189,7 @@
             .logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED")
 
     override val defaultDataSubRatConfig: StateFlow<Config> =
-        merge(defaultDataSubIdChangedEvent, carrierConfigChangedEvent)
+        merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
             .mapLatest { Config.readConfig(context) }
             .distinctUntilChanged()
             .logInputChange(logger, "defaultDataSubRatConfig")
@@ -258,6 +277,26 @@
             .logInputChange(logger, "defaultMobileNetworkConnectivity")
             .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
 
+    /**
+     * Flow that tracks the active mobile data subscriptions. Emits `true` whenever the active data
+     * subscription Id changes but the subscription group remains the same. In these cases, we want
+     * to retain the previous subscription's validation status for up to 2s to avoid flickering the
+     * icon.
+     *
+     * TODO(b/265164432): we should probably expose all change events, not just same group
+     */
+    @SuppressLint("MissingPermission")
+    override val activeSubChangedInGroupEvent =
+        activeMobileDataSubscriptionId
+            .pairwise()
+            .mapNotNull { (prevVal: Int, newVal: Int) ->
+                val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevVal)?.groupUuid
+                val nextSub = subscriptionManager.getActiveSubscriptionInfo(newVal)?.groupUuid
+
+                if (prevSub != null && prevSub == nextSub) Unit else null
+            }
+            .flowOn(bgDispatcher)
+
     private fun isValidSubId(subId: Int): Boolean {
         subscriptions.value.forEach {
             if (it.subscriptionId == subId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 003df24..9cdff96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -18,9 +18,11 @@
 
 import android.telephony.CarrierConfigManager
 import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons.NOT_DEFAULT_DATA
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
@@ -41,12 +43,29 @@
     /** The current mobile data activity */
     val activity: Flow<DataActivityModel>
 
+    /**
+     * This bit is meant to be `true` if and only if the default network capabilities (see
+     * [android.net.ConnectivityManager.registerDefaultNetworkCallback]) result in a network that
+     * has the [android.net.NetworkCapabilities.TRANSPORT_CELLULAR] represented.
+     *
+     * Note that this differs from [isDataConnected], which is tracked by telephony and has to do
+     * with the state of using this mobile connection for data as opposed to just voice. It is
+     * possible for a mobile subscription to be connected but not be in a connected data state, and
+     * thus we wouldn't want to show the network type icon.
+     */
+    val isConnected: Flow<Boolean>
+
+    /**
+     * True when telephony tells us that the data state is CONNECTED. See
+     * [android.telephony.TelephonyCallback.DataConnectionStateListener] for more details. We
+     * consider this connection to be serving data, and thus want to show a network type icon, when
+     * data is connected. Other data connection states would typically cause us not to show the icon
+     */
+    val isDataConnected: StateFlow<Boolean>
+
     /** Only true if mobile is the default transport but is not validated, otherwise false */
     val isDefaultConnectionFailed: StateFlow<Boolean>
 
-    /** True when telephony tells us that the data state is CONNECTED */
-    val isDataConnected: StateFlow<Boolean>
-
     /** True if we consider this connection to be in service, i.e. can make calls */
     val isInService: StateFlow<Boolean>
 
@@ -100,8 +119,10 @@
     defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
     override val alwaysShowDataRatIcon: StateFlow<Boolean>,
     override val alwaysUseCdmaLevel: StateFlow<Boolean>,
+    defaultMobileConnectivity: StateFlow<MobileConnectivityModel>,
     defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
     defaultMobileIconGroup: StateFlow<MobileIconGroup>,
+    defaultDataSubId: StateFlow<Int>,
     override val isDefaultConnectionFailed: StateFlow<Boolean>,
     connectionRepository: MobileConnectionRepository,
 ) : MobileIconInteractor {
@@ -111,8 +132,19 @@
 
     override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
 
+    override val isConnected: Flow<Boolean> = defaultMobileConnectivity.mapLatest { it.isConnected }
+
     override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
 
+    private val isDefault =
+        defaultDataSubId
+            .mapLatest { connectionRepository.subId == it }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                connectionRepository.subId == defaultDataSubId.value
+            )
+
     override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
 
     override val networkName =
@@ -137,7 +169,12 @@
                 connectionInfo,
                 defaultMobileIconMapping,
                 defaultMobileIconGroup,
-            ) { info, mapping, defaultGroup ->
+                isDefault,
+            ) { info, mapping, defaultGroup, isDefault ->
+                if (!isDefault) {
+                    return@combine NOT_DEFAULT_DATA
+                }
+
                 when (info.resolvedNetworkType) {
                     is ResolvedNetworkType.CarrierMergedNetworkType ->
                         info.resolvedNetworkType.iconGroupOverride
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 83da1dd..0e4a432 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -23,22 +23,29 @@
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.util.CarrierConfigTracker
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
 
 /**
  * Business layer logic for the set of mobile subscription icons.
@@ -62,6 +69,17 @@
     /** True if the CDMA level should be preferred over the primary level. */
     val alwaysUseCdmaLevel: StateFlow<Boolean>
 
+    /** Tracks the subscriptionId set as the default for data connections */
+    val defaultDataSubId: StateFlow<Int>
+
+    /**
+     * The connectivity of the default mobile network. Note that this can differ from what is
+     * reported from [MobileConnectionsRepository] in some cases. E.g., when the active subscription
+     * changes but the groupUuid remains the same, we keep the old validation information for 2
+     * seconds to avoid icon flickering.
+     */
+    val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
+
     /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
     val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
     /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
@@ -85,6 +103,7 @@
 constructor(
     private val mobileConnectionsRepo: MobileConnectionsRepository,
     private val carrierConfigTracker: CarrierConfigTracker,
+    private val logger: ConnectivityPipelineLogger,
     userSetupRepo: UserSetupRepository,
     @Application private val scope: CoroutineScope,
 ) : MobileIconsInteractor {
@@ -153,6 +172,50 @@
                 }
             }
         }
+            .distinctUntilChanged()
+            .onEach { logger.logFilteredSubscriptionsChanged(it) }
+
+    override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
+
+    /**
+     * Copied from the old pipeline. We maintain a 2s period of time where we will keep the
+     * validated bit from the old active network (A) while data is changing to the new one (B).
+     *
+     * This condition only applies if
+     * 1. A and B are in the same subscription group (e.c. for CBRS data switching) and
+     * 2. A was validated before the switch
+     *
+     * The goal of this is to minimize the flickering in the UI of the cellular indicator
+     */
+    private val forcingCellularValidation =
+        mobileConnectionsRepo.activeSubChangedInGroupEvent
+            .filter { mobileConnectionsRepo.defaultMobileNetworkConnectivity.value.isValidated }
+            .transformLatest {
+                emit(true)
+                delay(2000)
+                emit(false)
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+        combine(
+                mobileConnectionsRepo.defaultMobileNetworkConnectivity,
+                forcingCellularValidation,
+            ) { networkConnectivity, forceValidation ->
+                return@combine if (forceValidation) {
+                    MobileConnectivityModel(
+                        isValidated = true,
+                        isConnected = networkConnectivity.isConnected
+                    )
+                } else {
+                    networkConnectivity
+                }
+            }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                mobileConnectionsRepo.defaultMobileNetworkConnectivity.value
+            )
 
     /**
      * Mapping from network type to [MobileIconGroup] using the config generated for the default
@@ -207,8 +270,10 @@
             activeDataConnectionHasDataEnabled,
             alwaysShowDataRatIcon,
             alwaysUseCdmaLevel,
+            defaultMobileNetworkConnectivity,
             defaultMobileIconMapping,
             defaultMobileIconGroup,
+            defaultDataSubId,
             isDefaultConnectionFailed,
             mobileConnectionsRepo.getRepoForSubId(subId),
         )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 829a5ca..ef75713 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,7 +32,9 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -51,13 +55,17 @@
     interactor: MobileIconsInteractor,
     private val iconController: StatusBarIconController,
     private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+    private val logger: ConnectivityPipelineLogger,
     @Application private val scope: CoroutineScope,
     private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) : CoreStartable {
     private val mobileSubIds: Flow<List<Int>> =
-        interactor.filteredSubscriptions.mapLatest { subscriptions ->
-            subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
-        }
+        interactor.filteredSubscriptions
+            .mapLatest { subscriptions ->
+                subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+            }
+            .distinctUntilChanged()
+            .onEach { logger.logUiAdapterSubIdsUpdated(it) }
 
     /**
      * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
@@ -72,6 +80,9 @@
     /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
     val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
 
+    private var isCollecting: Boolean = false
+    private var lastValue: List<Int>? = null
+
     override fun start() {
         // Only notify the icon controller if we want to *render* the new icons.
         // Note that this flow may still run if
@@ -79,8 +90,18 @@
         // get the logging data without rendering.
         if (statusBarPipelineFlags.useNewMobileIcons()) {
             scope.launch {
-                mobileSubIds.collectLatest { iconController.setNewMobileIconSubIds(it) }
+                isCollecting = true
+                mobileSubIds.collectLatest {
+                    logger.logUiAdapterSubIdsSentToIconController(it)
+                    lastValue = it
+                    iconController.setNewMobileIconSubIds(it)
+                }
             }
         }
     }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("isCollecting=$isCollecting")
+        pw.println("Last values sent to icon controller: $lastValue")
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 3e81c7c..a4b2abc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -29,6 +29,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.settingslib.graph.SignalDrawable
 import com.android.systemui.R
+import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.StatusBarIconView
@@ -97,6 +98,12 @@
                     }
                 }
 
+                launch {
+                    viewModel.contentDescription.distinctUntilChanged().collect {
+                        ContentDescriptionViewBinder.bind(it, view)
+                    }
+                }
+
                 // Set the network type icon
                 launch {
                     viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index a2117c7..9e2024a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
 import com.android.settingslib.graph.SignalDrawable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -43,6 +45,7 @@
     val subscriptionId: Int
     /** An int consumable by [SignalDrawable] for display */
     val iconId: Flow<Int>
+    val contentDescription: Flow<ContentDescription>
     val roaming: Flow<Boolean>
     /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
     val networkTypeIcon: Flow<Icon?>
@@ -102,24 +105,46 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
     }
 
+    override val contentDescription: Flow<ContentDescription> = run {
+        val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH_NONE)
+        combine(
+                iconInteractor.level,
+                iconInteractor.isInService,
+            ) { level, isInService ->
+                val resId =
+                    when {
+                        isInService -> PHONE_SIGNAL_STRENGTH[level]
+                        else -> PHONE_SIGNAL_STRENGTH_NONE
+                    }
+                ContentDescription.Resource(resId)
+            }
+            .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+    }
+
+    private val showNetworkTypeIcon: Flow<Boolean> =
+        combine(
+            iconInteractor.isDataConnected,
+            iconInteractor.isDataEnabled,
+            iconInteractor.isDefaultConnectionFailed,
+            iconInteractor.alwaysShowDataRatIcon,
+            iconInteractor.isConnected,
+        ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
+            alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
+        }
+
     override val networkTypeIcon: Flow<Icon?> =
         combine(
                 iconInteractor.networkTypeIconGroup,
-                iconInteractor.isDataConnected,
-                iconInteractor.isDataEnabled,
-                iconInteractor.isDefaultConnectionFailed,
-                iconInteractor.alwaysShowDataRatIcon,
-            ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
+                showNetworkTypeIcon,
+            ) { networkTypeIconGroup, shouldShow ->
                 val desc =
                     if (networkTypeIconGroup.dataContentDescription != 0)
                         ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
                     else null
                 val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
                 return@combine when {
-                    alwaysShow -> icon
-                    !dataConnected -> null
-                    !dataEnabled -> null
-                    failedConnection -> null
+                    !shouldShow -> null
                     else -> icon
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index d3ff357..7c7ffaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.log.dagger.StatusBarConnectivityLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -97,15 +98,20 @@
         )
     }
 
-    fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
+    fun logOnCapabilitiesChanged(
+        network: Network,
+        networkCapabilities: NetworkCapabilities,
+        isDefaultNetworkCallback: Boolean,
+    ) {
         buffer.log(
             SB_LOGGING_TAG,
             LogLevel.INFO,
             {
+                bool1 = isDefaultNetworkCallback
                 int1 = network.getNetId()
                 str1 = networkCapabilities.toString()
             },
-            { "onCapabilitiesChanged: net=$int1 capabilities=$str1" }
+            { "onCapabilitiesChanged[default=$bool1]: net=$int1 capabilities=$str1" }
         )
     }
 
@@ -196,6 +202,35 @@
         )
     }
 
+    // TODO(b/238425913): We should split this class into mobile-specific and wifi-specific loggers.
+
+    fun logFilteredSubscriptionsChanged(subs: List<SubscriptionModel>) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            { str1 = subs.toString() },
+            { "Filtered subscriptions updated: $str1" },
+        )
+    }
+
+    fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            { str1 = subs.toString() },
+            { "Sub IDs in MobileUiAdapter updated internally: $str1" },
+        )
+    }
+
+    fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            { str1 = subs.toString() },
+            { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
+        )
+    }
+
     companion object {
         const val SB_LOGGING_TAG = "SbConnectivity"
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index cc0ec54..b1e2812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -77,6 +77,17 @@
         return binding.getShouldIconBeVisible()
     }
 
+    /** See [StatusBarIconView.getDrawingRect]. */
+    override fun getDrawingRect(outRect: Rect) {
+        super.getDrawingRect(outRect)
+        val translationX = translationX.toInt()
+        val translationY = translationY.toInt()
+        outRect.left += translationX
+        outRect.right += translationX
+        outRect.top += translationY
+        outRect.bottom += translationY
+    }
+
     /**
      * Initializes this view.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index d26499c..8669047 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -114,13 +114,17 @@
                             network: Network,
                             networkCapabilities: NetworkCapabilities
                         ) {
+                            logger.logOnCapabilitiesChanged(
+                                network,
+                                networkCapabilities,
+                                isDefaultNetworkCallback = true,
+                            )
+
                             // This method will always be called immediately after the network
                             // becomes the default, in addition to any time the capabilities change
                             // while the network is the default.
-                            // If this network contains valid wifi info, then wifi is the default
-                            // network.
-                            val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
-                            trySend(wifiInfo != null)
+                            // If this network is a wifi network, then wifi is the default network.
+                            trySend(isWifiNetwork(networkCapabilities))
                         }
 
                         override fun onLost(network: Network) {
@@ -152,7 +156,11 @@
                             network: Network,
                             networkCapabilities: NetworkCapabilities
                         ) {
-                            logger.logOnCapabilitiesChanged(network, networkCapabilities)
+                            logger.logOnCapabilitiesChanged(
+                                network,
+                                networkCapabilities,
+                                isDefaultNetworkCallback = false,
+                            )
 
                             wifiNetworkChangeEvents.tryEmit(Unit)
 
@@ -253,16 +261,30 @@
             networkCapabilities: NetworkCapabilities
         ): WifiInfo? {
             return when {
-                networkCapabilities.hasTransport(TRANSPORT_WIFI) ->
-                    networkCapabilities.transportInfo as WifiInfo?
                 networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
                     // Sometimes, cellular networks can act as wifi networks (known as VCN --
                     // virtual carrier network). So, see if this cellular network has wifi info.
                     Utils.tryGetWifiInfoForVcn(networkCapabilities)
+                networkCapabilities.hasTransport(TRANSPORT_WIFI) ->
+                    if (networkCapabilities.transportInfo is WifiInfo) {
+                        networkCapabilities.transportInfo as WifiInfo
+                    } else {
+                        null
+                    }
                 else -> null
             }
         }
 
+        /** True if these capabilities represent a wifi network. */
+        private fun isWifiNetwork(networkCapabilities: NetworkCapabilities): Boolean {
+            return when {
+                networkCapabilities.hasTransport(TRANSPORT_WIFI) -> true
+                networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ->
+                    Utils.tryGetWifiInfoForVcn(networkCapabilities) != null
+                else -> false
+            }
+        }
+
         private fun createWifiNetworkModel(
             wifiInfo: WifiInfo,
             network: Network,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
deleted file mode 100644
index 21a8300..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2016 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.systemui.statusbar.policy;
-
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.provider.Settings;
-import android.telephony.SubscriptionInfo;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Dependency;
-
-import java.util.List;
-
-public class EmergencyCryptkeeperText extends TextView {
-
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onPhoneStateChanged(int phoneState) {
-            update();
-        }
-
-        @Override
-        public void onRefreshCarrierInfo() {
-            update();
-        }
-    };
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) {
-                update();
-            }
-        }
-    };
-
-    public EmergencyCryptkeeperText(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        setVisibility(GONE);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-        mKeyguardUpdateMonitor.registerCallback(mCallback);
-        getContext().registerReceiver(mReceiver,
-                new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
-        update();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        if (mKeyguardUpdateMonitor != null) {
-            mKeyguardUpdateMonitor.removeCallback(mCallback);
-        }
-        getContext().unregisterReceiver(mReceiver);
-    }
-
-    private boolean iccCardExist(int simState) {
-        return ((simState == TelephonyManager.SIM_STATE_PIN_REQUIRED)
-                || (simState == TelephonyManager.SIM_STATE_PUK_REQUIRED)
-                || (simState == TelephonyManager.SIM_STATE_NETWORK_LOCKED)
-                || (simState == TelephonyManager.SIM_STATE_READY)
-                || (simState == TelephonyManager.SIM_STATE_NOT_READY)
-                || (simState == TelephonyManager.SIM_STATE_PERM_DISABLED)
-                || (simState == TelephonyManager.SIM_STATE_CARD_IO_ERROR)
-                || (simState == TelephonyManager.SIM_STATE_CARD_RESTRICTED)
-                || (simState == TelephonyManager.SIM_STATE_LOADED));
-    }
-
-    public void update() {
-        boolean hasMobile = mContext.getSystemService(TelephonyManager.class).isDataCapable();
-        boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
-
-        if (!hasMobile || airplaneMode) {
-            setText(null);
-            setVisibility(GONE);
-            return;
-        }
-
-        boolean allSimsMissing = true;
-        CharSequence displayText = null;
-
-        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getFilteredSubscriptionInfo();
-        final int N = subs.size();
-        for (int i = 0; i < N; i++) {
-            int subId = subs.get(i).getSubscriptionId();
-            int simState = mKeyguardUpdateMonitor.getSimState(subId);
-            CharSequence carrierName = subs.get(i).getCarrierName();
-            if (iccCardExist(simState) && !TextUtils.isEmpty(carrierName)) {
-                allSimsMissing = false;
-                displayText = carrierName;
-            }
-        }
-        if (allSimsMissing) {
-            if (N != 0) {
-                // Shows "Emergency calls only" on devices that are voice-capable.
-                // This depends on mPlmn containing the text "Emergency calls only" when the radio
-                // has some connectivity. Otherwise it should show "No service"
-                // Grab the first subscription, because they all should contain the emergency text,
-                // described above.
-                displayText = subs.get(0).getCarrierName();
-            } else {
-                // We don't have a SubscriptionInfo to get the emergency calls only from.
-                // Grab it from the old sticky broadcast if possible instead. We can use it
-                // here because no subscriptions are active, so we don't have
-                // to worry about MSIM clashing.
-                displayText = getContext().getText(
-                        com.android.internal.R.string.emergency_calls_only);
-                Intent i = getContext().registerReceiver(null,
-                        new IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED));
-                if (i != null) {
-                    displayText = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
-                }
-            }
-        }
-
-        setText(displayText);
-        setVisibility(TextUtils.isEmpty(displayText) ? GONE : VISIBLE);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EncryptionHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EncryptionHelper.java
deleted file mode 100644
index 9c099f9..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EncryptionHelper.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 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.systemui.statusbar.policy;
-
-import android.sysprop.VoldProperties;
-
-/**
- * Helper for determining whether the phone is decrypted yet.
- */
-public class EncryptionHelper {
-
-    public static final boolean IS_DATA_ENCRYPTED = isDataEncrypted();
-
-    private static boolean isDataEncrypted() {
-        String voldState = VoldProperties.decrypt().orElse("");
-        return "1".equals(voldState) || "trigger_restart_min_framework".equals(voldState);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 9946b4b..5dcafb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.policy;
 
 import android.annotation.WorkerThread;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
@@ -255,7 +254,6 @@
                 setTorchMode(enabled);
                 mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
                 mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
-                mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index f8c17e8..4866f73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -303,7 +303,8 @@
                     mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null
                             && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
                     if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
-                        mController.removeRemoteInput(mEntry, mToken);
+                        // Pass null to ensure all inputs are cleared for this entry b/227115380
+                        mController.removeRemoteInput(mEntry, null);
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index e0d780a..7a4e35f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -48,6 +48,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
@@ -73,6 +74,7 @@
     private boolean mIsAttached;
 
     private final ViewGroup mStatusBarWindowView;
+    private final FragmentService mFragmentService;
     // The container in which we should run launch animations started from the status bar and
     //   expanding into the opening window.
     private final ViewGroup mLaunchAnimationContainer;
@@ -86,6 +88,7 @@
             WindowManager windowManager,
             IWindowManager iWindowManager,
             StatusBarContentInsetsProvider contentInsetsProvider,
+            FragmentService fragmentService,
             @Main Resources resources,
             Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
         mContext = context;
@@ -93,6 +96,7 @@
         mIWindowManager = iWindowManager;
         mContentInsetsProvider = contentInsetsProvider;
         mStatusBarWindowView = statusBarWindowView;
+        mFragmentService = fragmentService;
         mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
                 R.id.status_bar_launch_animation_container);
         mLpChanged = new WindowManager.LayoutParams();
@@ -157,7 +161,7 @@
 
     /** Returns a fragment host manager for the status bar window view. */
     public FragmentHostManager getFragmentHostManager() {
-        return FragmentHostManager.get(mStatusBarWindowView);
+        return mFragmentService.getFragmentHostManager(mStatusBarWindowView);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
index 60f6df6..8f424b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
@@ -61,6 +61,10 @@
         listeners.add(listener)
     }
 
+    fun removeListener(listener: StatusBarWindowStateListener) {
+        listeners.remove(listener)
+    }
+
     /** Returns true if the window is currently showing. */
     fun windowIsShowing() = windowState == WINDOW_STATE_SHOWING
 
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
index 5a8850a..dde2a80 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
@@ -39,6 +39,17 @@
     private val featureFlags: FeatureFlags,
 ) : CoreStartable, StylusManager.StylusCallback, StylusManager.StylusBatteryCallback {
 
+    override fun onStylusAdded(deviceId: Int) {
+        // On some devices, the addition of a new internal stylus indicates the use of a
+        // USI stylus with a different vendor/product ID. We would therefore like to reset
+        // the battery notification suppression, in case the user has dismissed a low battery
+        // notification of the previous stylus.
+        val device = inputManager.getInputDevice(deviceId) ?: return
+        if (!device.isExternal) {
+            stylusUsiPowerUi.updateSuppression(false)
+        }
+    }
+
     override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {
         stylusUsiPowerUi.refresh()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
new file mode 100644
index 0000000..e092f01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.systemui.telephony.ui.activity
+
+import android.app.ActivityOptions
+import android.content.DialogInterface
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager
+import com.android.internal.app.AlertActivity
+import com.android.systemui.R
+
+/** Dialog shown to the user to switch to managed profile for making a call using work SIM. */
+class SwitchToManagedProfileForCallActivity : AlertActivity(), DialogInterface.OnClickListener {
+    private lateinit var phoneNumber: Uri
+    private var managedProfileUserId = UserHandle.USER_NULL
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        window.addSystemFlags(
+            WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+        )
+        super.onCreate(savedInstanceState)
+
+        phoneNumber = intent.getData()
+        managedProfileUserId =
+            intent.getIntExtra(
+                "android.telecom.extra.MANAGED_PROFILE_USER_ID",
+                UserHandle.USER_NULL
+            )
+
+        mAlertParams.apply {
+            mTitle = getString(R.string.call_from_work_profile_title)
+            mMessage = getString(R.string.call_from_work_profile_text)
+            mPositiveButtonText = getString(R.string.call_from_work_profile_action)
+            mNegativeButtonText = getString(R.string.call_from_work_profile_close)
+            mPositiveButtonListener = this@SwitchToManagedProfileForCallActivity
+            mNegativeButtonListener = this@SwitchToManagedProfileForCallActivity
+        }
+        setupAlert()
+    }
+
+    override fun onClick(dialog: DialogInterface?, which: Int) {
+        if (which == BUTTON_POSITIVE) {
+            switchToManagedProfile()
+        }
+        finish()
+    }
+
+    private fun switchToManagedProfile() {
+        try {
+            applicationContext.startActivityAsUser(
+                Intent(Intent.ACTION_DIAL, phoneNumber),
+                ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(),
+                UserHandle.of(managedProfileUserId)
+            )
+        } catch (e: Exception) {
+            Log.e(TAG, "Failed to launch activity", e)
+        }
+    }
+
+    companion object {
+        private const val TAG = "SwitchToManagedProfileForCallActivity"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index ad48e21..1065d33 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -31,6 +31,7 @@
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
 import androidx.annotation.CallSuper
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.CoreStartable
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.qualifiers.Main
@@ -108,9 +109,10 @@
      * Whenever the current view disappears, the next-priority view will be displayed if it's still
      * valid.
      */
+    @VisibleForTesting
     internal val activeViews: MutableList<DisplayInfo> = mutableListOf()
 
-    private fun getCurrentDisplayInfo(): DisplayInfo? {
+    internal fun getCurrentDisplayInfo(): DisplayInfo? {
         return activeViews.getOrNull(0)
     }
 
@@ -119,15 +121,26 @@
         dumpManager.registerNormalDumpable(this)
     }
 
+    private val listeners: MutableSet<Listener> = mutableSetOf()
+
+    /** Registers a listener. */
+    fun registerListener(listener: Listener) {
+        listeners.add(listener)
+    }
+
+    /** Unregisters a listener. */
+    fun unregisterListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
     /**
      * Displays the view with the provided [newInfo].
      *
      * This method handles inflating and attaching the view, then delegates to [updateView] to
      * display the correct information in the view.
-     * @param onViewTimeout a runnable that runs after the view timeout.
      */
     @Synchronized
-    fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
+    fun displayView(newInfo: T) {
         val timeout = accessibilityManager.getRecommendedTimeoutMillis(
             newInfo.timeoutMs,
             // Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
@@ -146,14 +159,13 @@
             logger.logViewUpdate(newInfo)
             currentDisplayInfo.info = newInfo
             currentDisplayInfo.timeExpirationMillis = timeExpirationMillis
-            updateTimeout(currentDisplayInfo, timeout, onViewTimeout)
+            updateTimeout(currentDisplayInfo, timeout)
             updateView(newInfo, view)
             return
         }
 
         val newDisplayInfo = DisplayInfo(
             info = newInfo,
-            onViewTimeout = onViewTimeout,
             timeExpirationMillis = timeExpirationMillis,
             // Null values will be updated to non-null if/when this view actually gets displayed
             view = null,
@@ -196,7 +208,7 @@
     private fun showNewView(newDisplayInfo: DisplayInfo, timeout: Int) {
         logger.logViewAddition(newDisplayInfo.info)
         createAndAcquireWakeLock(newDisplayInfo)
-        updateTimeout(newDisplayInfo, timeout, newDisplayInfo.onViewTimeout)
+        updateTimeout(newDisplayInfo, timeout)
         inflateAndUpdateView(newDisplayInfo)
     }
 
@@ -227,19 +239,16 @@
     /**
      * Creates a runnable that will remove [displayInfo] in [timeout] ms from now.
      *
-     * @param onViewTimeout an optional runnable that will be run if the view times out.
      * @return a runnable that, when run, will *cancel* the view's timeout.
      */
-    private fun updateTimeout(displayInfo: DisplayInfo, timeout: Int, onViewTimeout: Runnable?) {
+    private fun updateTimeout(displayInfo: DisplayInfo, timeout: Int) {
         val cancelViewTimeout = mainExecutor.executeDelayed(
             {
                 removeView(displayInfo.info.id, REMOVAL_REASON_TIMEOUT)
-                onViewTimeout?.run()
             },
             timeout.toLong()
         )
 
-        displayInfo.onViewTimeout = onViewTimeout
         // Cancel old view timeout and re-set it.
         displayInfo.cancelViewTimeout?.run()
         displayInfo.cancelViewTimeout = cancelViewTimeout
@@ -317,6 +326,9 @@
         // event comes in while this view is animating out, we still display the new view
         // appropriately.
         activeViews.remove(displayInfo)
+        listeners.forEach {
+            it.onInfoPermanentlyRemoved(id, removalReason)
+        }
 
         // No need to time the view out since it's already gone
         displayInfo.cancelViewTimeout?.run()
@@ -380,6 +392,9 @@
         invalidViews.forEach {
             activeViews.remove(it)
             logger.logViewExpiration(it.info)
+            listeners.forEach { listener ->
+                listener.onInfoPermanentlyRemoved(it.info.id, REMOVAL_REASON_TIME_EXPIRED)
+            }
         }
     }
 
@@ -436,6 +451,15 @@
         onAnimationEnd.run()
     }
 
+    /** A listener interface to be notified of various view events. */
+    fun interface Listener {
+        /**
+         * Called whenever a [DisplayInfo] with the given [id] has been removed and will never be
+         * displayed again (unless another call to [updateView] is made).
+         */
+        fun onInfoPermanentlyRemoved(id: String, reason: String)
+    }
+
     /** A container for all the display-related state objects. */
     inner class DisplayInfo(
         /**
@@ -461,11 +485,6 @@
         var wakeLock: WakeLock?,
 
         /**
-         * See [displayView].
-         */
-        var onViewTimeout: Runnable?,
-
-        /**
          * A runnable that, when run, will cancel this view's timeout.
          *
          * Null if this info isn't currently being displayed.
@@ -475,6 +494,7 @@
 }
 
 private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
+private const val REMOVAL_REASON_TIME_EXPIRED = "TIMEOUT_EXPIRED_BEFORE_REDISPLAY"
 private const val MIN_REQUIRED_TIME_FOR_REDISPLAY = 1000
 
 private data class IconInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index ec6965a..899b0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -80,6 +80,26 @@
         )
     }
 
+    /** Logs that there was a failure to animate the view in. */
+    fun logAnimateInFailure() {
+        buffer.log(
+            tag,
+            LogLevel.WARNING,
+            {},
+            { "View's appearance animation failed. Forcing view display manually." },
+        )
+    }
+
+    /** Logs that there was a failure to animate the view out. */
+    fun logAnimateOutFailure() {
+        buffer.log(
+            tag,
+            LogLevel.WARNING,
+            {},
+            { "View's disappearance animation failed." },
+        )
+    }
+
     fun logViewHidden(info: T) {
         buffer.log(
             tag,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
new file mode 100644
index 0000000..01a81de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarAnimator.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.systemui.temporarydisplay.chipbar
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.ViewHierarchyAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.children
+import javax.inject.Inject
+
+/**
+ * A class controlling chipbar animations. Typically delegates to [ViewHierarchyAnimator].
+ *
+ * Used so that animations can be mocked out in tests.
+ */
+@SysUISingleton
+open class ChipbarAnimator @Inject constructor() {
+    /**
+     * Animates [innerView] and its children into view.
+     *
+     * @return true if the animation was successfully started and false if the animation can't be
+     * run for any reason.
+     *
+     * See [ViewHierarchyAnimator.animateAddition].
+     */
+    open fun animateViewIn(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+        return ViewHierarchyAnimator.animateAddition(
+            innerView,
+            ViewHierarchyAnimator.Hotspot.TOP,
+            Interpolators.EMPHASIZED_DECELERATE,
+            duration = ANIMATION_IN_DURATION,
+            includeMargins = true,
+            includeFadeIn = true,
+            onAnimationEnd = onAnimationEnd,
+        )
+    }
+
+    /**
+     * Animates [innerView] and its children out of view.
+     *
+     * @return true if the animation was successfully started and false if the animation can't be
+     * run for any reason.
+     *
+     * See [ViewHierarchyAnimator.animateRemoval].
+     */
+    open fun animateViewOut(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+        return ViewHierarchyAnimator.animateRemoval(
+            innerView,
+            ViewHierarchyAnimator.Hotspot.TOP,
+            Interpolators.EMPHASIZED_ACCELERATE,
+            ANIMATION_OUT_DURATION,
+            includeMargins = true,
+            onAnimationEnd,
+        )
+    }
+
+    /** Force shows this view and all child views. Should be used in case [animateViewIn] fails. */
+    fun forceDisplayView(innerView: View) {
+        innerView.alpha = 1f
+        if (innerView is ViewGroup) {
+            innerView.children.forEach { forceDisplayView(it) }
+        }
+    }
+}
+
+private const val ANIMATION_IN_DURATION = 500L
+private const val ANIMATION_OUT_DURATION = 250L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 04b1a50..696134c 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -32,8 +32,6 @@
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.R
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.animation.ViewHierarchyAnimator
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text.Companion.loadText
@@ -78,8 +76,10 @@
     configurationController: ConfigurationController,
     dumpManager: DumpManager,
     powerManager: PowerManager,
+    private val chipbarAnimator: ChipbarAnimator,
     private val falsingManager: FalsingManager,
     private val falsingCollector: FalsingCollector,
+    private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler?,
     private val viewUtil: ViewUtil,
     private val vibratorHelper: VibratorHelper,
     wakeLockBuilder: WakeLock.Builder,
@@ -105,6 +105,8 @@
         commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) }
 
     override fun updateView(newInfo: ChipbarInfo, currentView: ViewGroup) {
+        updateGestureListening()
+
         logger.logViewUpdate(
             newInfo.windowTitle,
             newInfo.text.loadText(context),
@@ -203,31 +205,67 @@
     }
 
     override fun animateViewIn(view: ViewGroup) {
-        ViewHierarchyAnimator.animateAddition(
-            view.getInnerView(),
-            ViewHierarchyAnimator.Hotspot.TOP,
-            Interpolators.EMPHASIZED_DECELERATE,
-            duration = ANIMATION_IN_DURATION,
-            includeMargins = true,
-            includeFadeIn = true,
-            // We can only request focus once the animation finishes.
-            onAnimationEnd = {
-                maybeGetAccessibilityFocus(view.getTag(INFO_TAG) as ChipbarInfo?, view)
-            },
-        )
+        // We can only request focus once the animation finishes.
+        val onAnimationEnd = Runnable {
+            maybeGetAccessibilityFocus(view.getTag(INFO_TAG) as ChipbarInfo?, view)
+        }
+        val animatedIn = chipbarAnimator.animateViewIn(view.getInnerView(), onAnimationEnd)
+
+        // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run and the
+        // views would remain un-displayed. So, just force-set/run those items immediately.
+        if (!animatedIn) {
+            logger.logAnimateInFailure()
+            chipbarAnimator.forceDisplayView(view.getInnerView())
+            onAnimationEnd.run()
+        }
     }
 
     override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
         val innerView = view.getInnerView()
         innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
-        ViewHierarchyAnimator.animateRemoval(
-            innerView,
-            ViewHierarchyAnimator.Hotspot.TOP,
-            Interpolators.EMPHASIZED_ACCELERATE,
-            ANIMATION_OUT_DURATION,
-            includeMargins = true,
-            onAnimationEnd,
-        )
+        val removed = chipbarAnimator.animateViewOut(innerView, onAnimationEnd)
+        // If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
+        // run it immediately.
+        if (!removed) {
+            logger.logAnimateOutFailure()
+            onAnimationEnd.run()
+        }
+
+        updateGestureListening()
+    }
+
+    private fun updateGestureListening() {
+        if (swipeChipbarAwayGestureHandler == null) {
+            return
+        }
+
+        val currentDisplayInfo = getCurrentDisplayInfo()
+        if (currentDisplayInfo != null && currentDisplayInfo.info.allowSwipeToDismiss) {
+            swipeChipbarAwayGestureHandler.setViewFetcher { currentDisplayInfo.view }
+            swipeChipbarAwayGestureHandler.addOnGestureDetectedCallback(TAG) {
+                onSwipeUpGestureDetected()
+            }
+        } else {
+            swipeChipbarAwayGestureHandler.resetViewFetcher()
+            swipeChipbarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
+        }
+    }
+
+    private fun onSwipeUpGestureDetected() {
+        val currentDisplayInfo = getCurrentDisplayInfo()
+        if (currentDisplayInfo == null) {
+            logger.logSwipeGestureError(id = null, errorMsg = "No info is being displayed")
+            return
+        }
+        if (!currentDisplayInfo.info.allowSwipeToDismiss) {
+            logger.logSwipeGestureError(
+                id = currentDisplayInfo.info.id,
+                errorMsg = "This view prohibits swipe-to-dismiss",
+            )
+            return
+        }
+        removeView(currentDisplayInfo.info.id, SWIPE_UP_GESTURE_REASON)
+        updateGestureListening()
     }
 
     private fun ViewGroup.getInnerView(): ViewGroup {
@@ -247,6 +285,6 @@
     }
 }
 
-private const val ANIMATION_IN_DURATION = 500L
-private const val ANIMATION_OUT_DURATION = 250L
 @IdRes private val INFO_TAG = R.id.tag_chipbar_info
+private const val SWIPE_UP_GESTURE_REASON = "SWIPE_UP_GESTURE_DETECTED"
+private const val TAG = "ChipbarCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index dd4bd26..fe46318 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -33,12 +33,14 @@
  * @property endItem an optional end item to display at the end of the chipbar (on the right in LTR
  * locales; on the left in RTL locales).
  * @property vibrationEffect an optional vibration effect when the chipbar is displayed
+ * @property allowSwipeToDismiss true if users are allowed to swipe up to dismiss this chipbar.
  */
 data class ChipbarInfo(
     val startIcon: TintedIcon,
     val text: Text,
     val endItem: ChipbarEndItem?,
     val vibrationEffect: VibrationEffect? = null,
+    val allowSwipeToDismiss: Boolean = false,
     override val windowTitle: String,
     override val wakeReason: String,
     override val timeoutMs: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index fcfbe0a..f239428 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -46,4 +46,16 @@
             { "Chipbar updated. window=$str1 text=$str2 endItem=$str3" }
         )
     }
+
+    fun logSwipeGestureError(id: String?, errorMsg: String) {
+        buffer.log(
+            tag,
+            LogLevel.WARNING,
+            {
+                str1 = id
+                str2 = errorMsg
+            },
+            { "Chipbar swipe gesture detected for incorrect state. id=$str1 error=$str2" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
new file mode 100644
index 0000000..9dbc4b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandler.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.systemui.temporarydisplay.chipbar
+
+import android.content.Context
+import android.view.MotionEvent
+import android.view.View
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler
+import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
+import com.android.systemui.util.boundsOnScreen
+
+/**
+ * A class to detect when a user has swiped the chipbar away.
+ *
+ * Effectively [SysUISingleton]. But, this shouldn't be created if the gesture isn't enabled. See
+ * [TemporaryDisplayModule.provideSwipeChipbarAwayGestureHandler].
+ */
+class SwipeChipbarAwayGestureHandler(
+    context: Context,
+    displayTracker: DisplayTracker,
+    logger: SwipeUpGestureLogger,
+) : SwipeUpGestureHandler(context, displayTracker, logger, loggerTag = LOGGER_TAG) {
+
+    private var viewFetcher: () -> View? = { null }
+
+    override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean {
+        val view = viewFetcher.invoke() ?: return false
+        // Since chipbar is in its own window, we need to use [boundsOnScreen] to get an accurate
+        // bottom. ([view.bottom] would be relative to its window, which would be too small.)
+        val viewBottom = view.boundsOnScreen.bottom
+        // Allow the gesture to start a bit below the chipbar
+        return ev.y <= 1.5 * viewBottom
+    }
+
+    /**
+     * Sets a fetcher that returns the current chipbar view. The fetcher will be invoked whenever a
+     * gesture starts to determine if the gesture is near the chipbar.
+     */
+    fun setViewFetcher(fetcher: () -> View?) {
+        viewFetcher = fetcher
+    }
+
+    /** Removes the current view fetcher. */
+    fun resetViewFetcher() {
+        viewFetcher = { null }
+    }
+}
+
+private const val LOGGER_TAG = "SwipeChipbarAway"
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
index cf0a183..b1be404 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -16,22 +16,40 @@
 
 package com.android.systemui.temporarydisplay.dagger
 
+import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
+import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
 import dagger.Module
 import dagger.Provides
 
 @Module
 interface TemporaryDisplayModule {
-    @Module
     companion object {
-        @JvmStatic
         @Provides
         @SysUISingleton
         @ChipbarLog
         fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
             return factory.create("ChipbarLog", 40)
         }
+
+        @Provides
+        @SysUISingleton
+        fun provideSwipeChipbarAwayGestureHandler(
+            mediaTttFlags: MediaTttFlags,
+            context: Context,
+            displayTracker: DisplayTracker,
+            logger: SwipeUpGestureLogger,
+        ): SwipeChipbarAwayGestureHandler? {
+            return if (mediaTttFlags.isMediaTttDismissGestureEnabled()) {
+                SwipeChipbarAwayGestureHandler(context, displayTracker, logger)
+            } else {
+                null
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 3ecb15b..2b7ea2a 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -26,7 +26,6 @@
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
 import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
 
-import android.annotation.Nullable;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.app.WallpaperManager.OnColorsChangedListener;
@@ -70,6 +69,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.monet.Style;
+import com.android.systemui.monet.TonalPalette;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -357,15 +357,24 @@
     };
 
     @Inject
-    public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
-            @Background Handler bgHandler, @Main Executor mainExecutor,
-            @Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
-            SecureSettings secureSettings, WallpaperManager wallpaperManager,
-            UserManager userManager, DeviceProvisionedController deviceProvisionedController,
-            UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
-            @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
+    public ThemeOverlayController(
+            Context context,
+            BroadcastDispatcher broadcastDispatcher,
+            @Background Handler bgHandler,
+            @Main Executor mainExecutor,
+            @Background Executor bgExecutor,
+            ThemeOverlayApplier themeOverlayApplier,
+            SecureSettings secureSettings,
+            WallpaperManager wallpaperManager,
+            UserManager userManager,
+            DeviceProvisionedController deviceProvisionedController,
+            UserTracker userTracker,
+            DumpManager dumpManager,
+            FeatureFlags featureFlags,
+            @Main Resources resources,
+            WakefulnessLifecycle wakefulnessLifecycle) {
         mContext = context;
-        mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEMES);
+        mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
         mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
         mDeviceProvisionedController = deviceProvisionedController;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -391,7 +400,7 @@
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor,
                 UserHandle.ALL);
         mSecureSettings.registerContentObserverForUser(
-                Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
+                Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                 false,
                 new ContentObserver(mBgHandler) {
                     @Override
@@ -513,39 +522,42 @@
     /**
      * Given a color candidate, return an overlay definition.
      */
-    protected @Nullable FabricatedOverlay getOverlay(int color, int type, Style style) {
+    protected FabricatedOverlay getOverlay(int color, int type, Style style) {
         boolean nightMode = (mResources.getConfiguration().uiMode
                 & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
 
         mColorScheme = new ColorScheme(color, nightMode, style);
-        List<Integer> colorShades = type == ACCENT
-                ? mColorScheme.getAllAccentColors() : mColorScheme.getAllNeutralColors();
         String name = type == ACCENT ? "accent" : "neutral";
-        int paletteSize = mColorScheme.getAccent1().size();
+
         FabricatedOverlay.Builder overlay =
                 new FabricatedOverlay.Builder("com.android.systemui", name, "android");
-        for (int i = 0; i < colorShades.size(); i++) {
-            int luminosity = i % paletteSize;
-            int paletteIndex = i / paletteSize + 1;
-            String resourceName;
-            switch (luminosity) {
-                case 0:
-                    resourceName = "android:color/system_" + name + paletteIndex + "_10";
-                    break;
-                case 1:
-                    resourceName = "android:color/system_" + name + paletteIndex + "_50";
-                    break;
-                default:
-                    int l = luminosity - 1;
-                    resourceName = "android:color/system_" + name + paletteIndex + "_" + l + "00";
-            }
-            overlay.setResourceValue(resourceName, TypedValue.TYPE_INT_COLOR_ARGB8,
-                    ColorUtils.setAlphaComponent(colorShades.get(i), 0xFF));
+
+        if (type == ACCENT) {
+            assignTonalPaletteToOverlay("accent1", overlay, mColorScheme.getAccent1());
+            assignTonalPaletteToOverlay("accent2", overlay, mColorScheme.getAccent2());
+            assignTonalPaletteToOverlay("accent3", overlay, mColorScheme.getAccent3());
+        } else {
+            assignTonalPaletteToOverlay("neutral1", overlay, mColorScheme.getNeutral1());
+            assignTonalPaletteToOverlay("neutral2", overlay, mColorScheme.getNeutral2());
         }
 
         return overlay.build();
     }
 
+    private void assignTonalPaletteToOverlay(String name,
+            FabricatedOverlay.Builder overlay, TonalPalette tonalPalette) {
+
+        String resourcePrefix = "android:color/system_" + name;
+        int colorDataType = TypedValue.TYPE_INT_COLOR_ARGB8;
+
+        tonalPalette.getAllShadesMapped().forEach((key, value) -> {
+            String resourceName = resourcePrefix + "_" + key;
+            int colorValue = ColorUtils.setAlphaComponent(value, 0xFF);
+            overlay.setResourceValue(resourceName, colorDataType,
+                    colorValue);
+        });
+    }
+
     /**
      * Checks if the color scheme in mColorScheme matches the current system palettes.
      * @param managedProfiles List of managed profiles for this user.
@@ -557,15 +569,15 @@
             Resources res = userHandle.isSystem()
                     ? mResources : mContext.createContextAsUser(userHandle, 0).getResources();
             if (!(res.getColor(android.R.color.system_accent1_500, mContext.getTheme())
-                    == mColorScheme.getAccent1().get(6)
+                    == mColorScheme.getAccent1().getS500()
                     && res.getColor(android.R.color.system_accent2_500, mContext.getTheme())
-                    == mColorScheme.getAccent2().get(6)
+                    == mColorScheme.getAccent2().getS500()
                     && res.getColor(android.R.color.system_accent3_500, mContext.getTheme())
-                    == mColorScheme.getAccent3().get(6)
+                    == mColorScheme.getAccent3().getS500()
                     && res.getColor(android.R.color.system_neutral1_500, mContext.getTheme())
-                    == mColorScheme.getNeutral1().get(6)
+                    == mColorScheme.getNeutral1().getS500()
                     && res.getColor(android.R.color.system_neutral2_500, mContext.getTheme())
-                    == mColorScheme.getNeutral2().get(6))) {
+                    == mColorScheme.getNeutral2().getS500())) {
                 return false;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
index 79811c5..2475890 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/RadioListPreference.java
@@ -28,8 +28,9 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentService;
 
 import java.util.Objects;
 
@@ -74,7 +75,7 @@
 
         RadioFragment f = new RadioFragment();
         f.setPreference(this);
-        FragmentHostManager.get(v).getFragmentManager()
+        Dependency.get(FragmentService.class).getFragmentHostManager(v).getFragmentManager()
                 .beginTransaction()
                 .add(android.R.id.content, f)
                 .commit();
@@ -86,8 +87,10 @@
             Bundle savedInstanceState) {
         super.onDialogStateRestored(fragment, dialog, savedInstanceState);
         View view = dialog.findViewById(R.id.content);
-        RadioFragment radioFragment = (RadioFragment) FragmentHostManager.get(view)
-                .getFragmentManager().findFragmentById(R.id.content);
+        RadioFragment radioFragment = (RadioFragment) Dependency.get(FragmentService.class)
+                .getFragmentHostManager(view)
+                .getFragmentManager()
+                .findFragmentById(R.id.content);
         if (radioFragment != null) {
             radioFragment.setPreference(this);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 7726d09..8214822 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -3,26 +3,43 @@
 import android.os.SystemProperties
 import android.os.VibrationEffect
 import android.os.Vibrator
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.Executor
 import javax.inject.Inject
 
-/**
- * Class that plays a haptics effect during unfolding a foldable device
- */
+/** Class that plays a haptics effect during unfolding a foldable device */
 @SysUIUnfoldScope
 class UnfoldHapticsPlayer
 @Inject
 constructor(
     unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+    foldProvider: FoldProvider,
+    @Main private val mainExecutor: Executor,
     private val vibrator: Vibrator?
 ) : TransitionProgressListener {
 
+    private var isFirstAnimationAfterUnfold = false
+
     init {
         if (vibrator != null) {
             // We don't need to remove the callback because we should listen to it
             // the whole time when SystemUI process is alive
             unfoldTransitionProgressProvider.addCallback(this)
         }
+
+        foldProvider.registerCallback(
+            object : FoldCallback {
+                override fun onFoldUpdated(isFolded: Boolean) {
+                    if (isFolded) {
+                        isFirstAnimationAfterUnfold = true
+                    }
+                }
+            },
+            mainExecutor
+        )
     }
 
     private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
@@ -36,6 +53,13 @@
     }
 
     override fun onTransitionFinishing() {
+        // Run haptics only when unfolding the device (first animation after unfolding)
+        if (!isFirstAnimationAfterUnfold) {
+            return
+        }
+
+        isFirstAnimationAfterUnfold = false
+
         // Run haptics only if the animation is long enough to notice
         if (lastTransitionProgress < TRANSITION_NOTICEABLE_THRESHOLD) {
             playHaptics()
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 523cf68..19a0866 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -36,6 +36,9 @@
 import android.view.WindowManager
 import android.view.WindowlessWindowManager
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.LinearLightRevealEffect
@@ -57,6 +60,7 @@
 @Inject
 constructor(
     private val context: Context,
+    private val featureFlags: FeatureFlags,
     private val deviceStateManager: DeviceStateManager,
     private val contentResolver: ContentResolver,
     private val displayManager: DisplayManager,
@@ -65,6 +69,7 @@
     @Main private val executor: Executor,
     private val threadFactory: ThreadFactory,
     private val rotationChangeProvider: RotationChangeProvider,
+    private val displayTracker: DisplayTracker
 ) {
 
     private val transitionListener = TransitionListener()
@@ -81,6 +86,7 @@
     private var scrimView: LightRevealScrim? = null
     private var isFolded: Boolean = false
     private var isUnfoldHandled: Boolean = true
+    private var overlayAddReason: AddOverlayReason? = null
 
     private var currentRotation: Int = context.display!!.rotation
 
@@ -100,7 +106,7 @@
                 .setName("unfold-overlay-container")
 
         displayAreaHelper.get().attachToRootDisplayArea(
-            Display.DEFAULT_DISPLAY,
+            displayTracker.defaultDisplayId,
             containerBuilder
         ) { builder ->
             executor.execute {
@@ -158,6 +164,8 @@
         ensureInBackground()
         ensureOverlayRemoved()
 
+        overlayAddReason = reason
+
         val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
         val params = getLayoutParams()
         val newView =
@@ -170,11 +178,7 @@
                 .apply {
                     revealEffect = createLightRevealEffect()
                     isScrimOpaqueChangedListener = Consumer {}
-                    revealAmount =
-                        when (reason) {
-                            FOLD -> TRANSPARENT
-                            UNFOLD -> BLACK
-                        }
+                    revealAmount = calculateRevealAmount()
                 }
 
         newRoot.setView(newView, params)
@@ -207,6 +211,31 @@
         root = newRoot
     }
 
+    private fun calculateRevealAmount(animationProgress: Float? = null): Float {
+        val overlayAddReason = overlayAddReason ?: UNFOLD
+
+        if (animationProgress == null) {
+            // Animation progress is unknown, calculate the initial value based on the overlay
+            // add reason
+            return when (overlayAddReason) {
+                FOLD -> TRANSPARENT
+                UNFOLD -> BLACK
+            }
+        }
+
+        val showVignetteWhenFolding =
+            featureFlags.isEnabled(Flags.ENABLE_DARK_VIGNETTE_WHEN_FOLDING)
+
+        return if (!showVignetteWhenFolding && overlayAddReason == FOLD) {
+            // Do not darken the content when SHOW_VIGNETTE_WHEN_FOLDING flag is off
+            // and we are folding the device. We still add the overlay to block touches
+            // while the animation is running but the overlay is transparent.
+            TRANSPARENT
+        } else {
+            animationProgress
+        }
+    }
+
     private fun getLayoutParams(): WindowManager.LayoutParams {
         val params: WindowManager.LayoutParams = WindowManager.LayoutParams()
 
@@ -259,7 +288,7 @@
     private inner class TransitionListener : TransitionProgressListener {
 
         override fun onTransitionProgress(progress: Float) {
-            executeInBackground { scrimView?.revealAmount = progress }
+            executeInBackground { scrimView?.revealAmount = calculateRevealAmount(progress) }
         }
 
         override fun onTransitionFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index c0f0390..e5ab473 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -17,8 +17,11 @@
 
 package com.android.systemui.user.data.repository
 
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
 import android.content.Context
 import android.content.pm.UserInfo
+import android.os.IRemoteCallback
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
@@ -30,6 +33,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.GlobalSettings
@@ -68,6 +73,9 @@
     /** [UserInfo] of the currently-selected user. */
     val selectedUserInfo: Flow<UserInfo>
 
+    /** Whether user switching is currently in progress. */
+    val userSwitchingInProgress: Flow<Boolean>
+
     /** User ID of the last non-guest selected user. */
     val lastSelectedNonGuestUserId: Int
 
@@ -108,6 +116,8 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val globalSettings: GlobalSettings,
     private val tracker: UserTracker,
+    private val activityManager: IActivityManager,
+    featureFlags: FeatureFlags,
 ) : UserRepository {
 
     private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
@@ -129,6 +139,10 @@
     private var _isGuestUserResetting: Boolean = false
     override var isGuestUserResetting: Boolean = _isGuestUserResetting
 
+    private val _isUserSwitchingInProgress = MutableStateFlow(false)
+    override val userSwitchingInProgress: Flow<Boolean>
+        get() = _isUserSwitchingInProgress
+
     override val isGuestUserCreationScheduled = AtomicBoolean()
 
     override val isStatusBarUserChipEnabled: Boolean =
@@ -141,6 +155,9 @@
     init {
         observeSelectedUser()
         observeUserSettings()
+        if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
+            observeUserSwitching()
+        }
     }
 
     override fun refreshUsers() {
@@ -166,6 +183,28 @@
         return _userSwitcherSettings.value.isSimpleUserSwitcher
     }
 
+    private fun observeUserSwitching() {
+        conflatedCallbackFlow {
+                val callback =
+                    object : UserSwitchObserver() {
+                        override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback) {
+                            trySendWithFailureLogging(true, TAG, "userSwitching started")
+                        }
+
+                        override fun onUserSwitchComplete(newUserId: Int) {
+                            trySendWithFailureLogging(false, TAG, "userSwitching completed")
+                        }
+                    }
+                activityManager.registerUserSwitchObserver(callback, TAG)
+                trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+                awaitClose { activityManager.unregisterUserSwitchObserver(callback) }
+            }
+            .onEach { _isUserSwitchingInProgress.value = it }
+            // TODO (b/262838215), Make this stateIn and initialize directly in field declaration
+            //  once the flag is launched
+            .launchIn(applicationScope)
+    }
+
     private fun observeSelectedUser() {
         conflatedCallbackFlow {
                 fun send() {
@@ -174,7 +213,7 @@
 
                 val callback =
                     object : UserTracker.Callback {
-                        override fun onUserChanged(newUser: Int, userContext: Context) {
+                        override fun onUserChanging(newUser: Int, userContext: Context) {
                             send()
                         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
index a9d66de..5a25a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/AddUserDialog.kt
@@ -63,11 +63,10 @@
                 }
 
                 // Use broadcast instead of ShadeController, as this dialog may have started in
-                // another
-                // process where normal dagger bindings are not available.
+                // another process where normal dagger bindings are not available.
                 broadcastSender.sendBroadcastAsUser(
                     Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
-                    UserHandle.CURRENT
+                    userHandle
                 )
 
                 context.startActivityAsUser(
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index d54de3fa..c2727fc 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -14,8 +14,6 @@
 
 package com.android.systemui.util;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import android.Manifest;
 import android.content.Context;
 import android.content.Intent;
@@ -26,6 +24,7 @@
 
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.util.List;
@@ -71,8 +70,9 @@
      * {@link android.view.WindowManagerPolicyConstants#NAV_BAR_MODE_GESTURAL} AND
      * the context is that of the default display
      */
-    public static boolean isGesturalModeOnDefaultDisplay(Context context, int navMode) {
-        return context.getDisplayId() == DEFAULT_DISPLAY
+    public static boolean isGesturalModeOnDefaultDisplay(Context context,
+            DisplayTracker displayTracker, int navMode) {
+        return context.getDisplayId() == displayTracker.getDefaultDisplayId()
                 && QuickStepContract.isGesturalMode(navMode);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 08ee0af..56c5d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -27,6 +27,8 @@
 import android.widget.TextView
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
 import com.android.systemui.statusbar.CrossFadeHelper
 
 /**
@@ -38,7 +40,7 @@
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0
-) : ConstraintLayout(context, attrs, defStyleAttr) {
+) : ConstraintLayout(context, attrs, defStyleAttr), LaunchableView {
 
     private val boundsRect = Rect()
     private val originalGoneChildrenSet: MutableSet<Int> = mutableSetOf()
@@ -50,7 +52,11 @@
 
     private var desiredMeasureWidth = 0
     private var desiredMeasureHeight = 0
-    private var transitionVisibility = View.VISIBLE
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
 
     /**
      * The measured state of this view which is the one we will lay ourselves out with. This
@@ -83,11 +89,12 @@
         }
     }
 
-    override fun setTransitionVisibility(visibility: Int) {
-        // We store the last transition visibility assigned to this view to restore it later if
-        // necessary.
-        super.setTransitionVisibility(visibility)
-        transitionVisibility = visibility
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        delegate.setVisibility(visibility)
     }
 
     override fun onFinishInflate() {
@@ -173,14 +180,6 @@
         translationY = currentState.translation.y
 
         CrossFadeHelper.fadeIn(this, currentState.alpha)
-
-        // CrossFadeHelper#fadeIn will change this view visibility, which overrides the transition
-        // visibility. We set the transition visibility again to make sure that this view plays well
-        // with GhostView, which sets the transition visibility and is used for activity launch
-        // animations.
-        if (transitionVisibility != View.VISIBLE) {
-            setTransitionVisibility(transitionVisibility)
-        }
     }
 
     private fun applyCurrentStateOnPredraw() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index b61b2e6..47d505e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -16,14 +16,21 @@
 
 package com.android.systemui.util.kotlin
 
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.time.SystemClockImpl
+import kotlinx.coroutines.CoroutineStart
 import java.util.concurrent.atomic.AtomicReference
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.channelFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
+import kotlin.math.max
 
 /**
  * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
@@ -167,3 +174,89 @@
  * Note that the returned Flow will not emit anything until [other] has emitted at least one value.
  */
 fun <A> Flow<*>.sample(other: Flow<A>): Flow<A> = sample(other) { _, a -> a }
+
+/**
+ * Returns a flow that mirrors the original flow, but delays values following emitted values for the
+ * given [periodMs]. If the original flow emits more than one value during this period, only the
+ * latest value is emitted.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * flow {
+ *     emit(1)     // t=0ms
+ *     delay(90)
+ *     emit(2)     // t=90ms
+ *     delay(90)
+ *     emit(3)     // t=180ms
+ *     delay(1010)
+ *     emit(4)     // t=1190ms
+ *     delay(1010)
+ *     emit(5)     // t=2200ms
+ * }.throttle(1000)
+ * ```
+ *
+ * produces the following emissions at the following times
+ *
+ * ```text
+ * 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
+ * ```
+ */
+fun <T> Flow<T>.throttle(periodMs: Long): Flow<T> = this.throttle(periodMs, SystemClockImpl())
+
+/**
+ * Returns a flow that mirrors the original flow, but delays values following emitted values for the
+ * given [periodMs] as reported by the given [clock]. If the original flow emits more than one value
+ * during this period, only The latest value is emitted.
+ *
+ * Example:
+ *
+ * ```kotlin
+ * flow {
+ *     emit(1)     // t=0ms
+ *     delay(90)
+ *     emit(2)     // t=90ms
+ *     delay(90)
+ *     emit(3)     // t=180ms
+ *     delay(1010)
+ *     emit(4)     // t=1190ms
+ *     delay(1010)
+ *     emit(5)     // t=2200ms
+ * }.throttle(1000)
+ * ```
+ *
+ * produces the following emissions at the following times
+ *
+ * ```text
+ * 1 (t=0ms), 3 (t=1000ms), 4 (t=2000ms), 5 (t=3000ms)
+ * ```
+ */
+fun <T> Flow<T>.throttle(periodMs: Long, clock: SystemClock): Flow<T> = channelFlow {
+    coroutineScope {
+        var previousEmitTimeMs = 0L
+        var delayJob: Job? = null
+        var sendJob: Job? = null
+        val outerScope = this
+
+        collect {
+            delayJob?.cancel()
+            sendJob?.join()
+            val currentTimeMs = clock.elapsedRealtime()
+            val timeSinceLastEmit = currentTimeMs - previousEmitTimeMs
+            val timeUntilNextEmit = max(0L, periodMs - timeSinceLastEmit)
+            if (timeUntilNextEmit > 0L) {
+                // We create delayJob to allow cancellation during the delay period
+                delayJob = launch {
+                    delay(timeUntilNextEmit)
+                    sendJob = outerScope.launch(start = CoroutineStart.UNDISPATCHED) {
+                        send(it)
+                        previousEmitTimeMs = clock.elapsedRealtime()
+                    }
+                }
+            } else {
+                send(it)
+                previousEmitTimeMs = currentTimeMs
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
index 4351afe..a0d22f3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
@@ -29,12 +29,12 @@
 import android.net.Uri;
 import android.os.Debug;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.util.Log;
 
 import androidx.core.content.FileProvider;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.settings.UserTracker;
 
 import com.google.android.collect.Lists;
 
@@ -62,13 +62,15 @@
     static final String LEAK_DUMP = "leak.dump";
 
     private final Context mContext;
+    private final UserTracker mUserTracker;
     private final LeakDetector mLeakDetector;
     private final String mLeakReportEmail;
 
     @Inject
-    public LeakReporter(Context context, LeakDetector leakDetector,
+    public LeakReporter(Context context, UserTracker userTracker, LeakDetector leakDetector,
             @Nullable @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) {
         mContext = context;
+        mUserTracker = userTracker;
         mLeakDetector = leakDetector;
         mLeakReportEmail = leakReportEmail;
     }
@@ -111,7 +113,7 @@
                             getIntent(hprofFile, dumpFile),
                             PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
                             null,
-                            UserHandle.CURRENT));
+                            mUserTracker.getUserHandle()));
             notiMan.notify(TAG, 0, builder.build());
         } catch (IOException e) {
             Log.e(TAG, "Couldn't dump heap for leak", e);
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java b/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java
index 0b2f004..31f35fc 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/RotationUtils.java
@@ -1,15 +1,17 @@
 /*
  * Copyright (C) 2017 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
+ * 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.
+ * 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.systemui.util.leak;
@@ -26,7 +28,27 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-public class RotationUtils {
+/**
+ * Utility class that provides device orientation.
+ *
+ * <p>Consider using {@link Surface.Rotation} or add a function that respects device aspect ratio
+ * and {@code android.internal.R.bool.config_reverseDefaultRotation}.
+ *
+ * <p>If you only care about the rotation, use {@link Surface.Rotation}, as it always gives the
+ * counter clock-wise rotation. (e.g. If you have a device that has a charging port at the bottom,
+ * rotating three times in counter clock direction will give you {@link Surface#ROTATION_270} while
+ * having the charging port on the left side of the device.)
+ *
+ * <p>If you need whether the device is in portrait or landscape (or their opposites), please add a
+ * function here that respects the device aspect ratio and
+ * {@code android.internal.R.bool.config_reverseDefaultRotation} together.
+ *
+ * <p>Note that {@code android.internal.R.bool.config_reverseDefaultRotation} does not change the
+ * winding order. In other words, the rotation order (counter clock-wise) will remain the same. It
+ * only flips the device orientation, such that portrait becomes upside down, landscape becomes
+ * seascape.
+ */
+public final class RotationUtils {
 
     public static final int ROTATION_NONE = 0;
     public static final int ROTATION_LANDSCAPE = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 1a30b0a..85fada2 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -20,14 +20,18 @@
 import android.net.Uri;
 import android.provider.Settings;
 
+import com.android.systemui.settings.UserTracker;
+
 import javax.inject.Inject;
 
 class GlobalSettingsImpl implements GlobalSettings {
     private final ContentResolver mContentResolver;
+    private final UserTracker mUserTracker;
 
     @Inject
-    GlobalSettingsImpl(ContentResolver contentResolver) {
+    GlobalSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
         mContentResolver = contentResolver;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -36,13 +40,19 @@
     }
 
     @Override
+    public UserTracker getUserTracker() {
+        return mUserTracker;
+    }
+
+    @Override
     public Uri getUriFor(String name) {
         return Settings.Global.getUriFor(name);
     }
 
     @Override
     public String getStringForUser(String name, int userHandle) {
-        return Settings.Global.getStringForUser(mContentResolver, name, userHandle);
+        return Settings.Global.getStringForUser(mContentResolver, name,
+                getRealUserHandle(userHandle));
     }
 
     @Override
@@ -53,14 +63,16 @@
 
     @Override
     public boolean putStringForUser(String name, String value, int userHandle) {
-        return Settings.Global.putStringForUser(mContentResolver, name, value, userHandle);
+        return Settings.Global.putStringForUser(mContentResolver, name, value,
+                getRealUserHandle(userHandle));
     }
 
     @Override
     public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
             int userHandle, boolean overrideableByRestore) {
         return Settings.Global.putStringForUser(
-                mContentResolver, name, value, tag, makeDefault, userHandle, overrideableByRestore);
+                mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle),
+                overrideableByRestore);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index 020c234..f995436 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -20,14 +20,18 @@
 import android.net.Uri;
 import android.provider.Settings;
 
+import com.android.systemui.settings.UserTracker;
+
 import javax.inject.Inject;
 
 class SecureSettingsImpl implements SecureSettings {
     private final ContentResolver mContentResolver;
+    private final UserTracker mUserTracker;
 
     @Inject
-    SecureSettingsImpl(ContentResolver contentResolver) {
+    SecureSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
         mContentResolver = contentResolver;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -36,13 +40,19 @@
     }
 
     @Override
+    public UserTracker getUserTracker() {
+        return mUserTracker;
+    }
+
+    @Override
     public Uri getUriFor(String name) {
         return Settings.Secure.getUriFor(name);
     }
 
     @Override
     public String getStringForUser(String name, int userHandle) {
-        return Settings.Secure.getStringForUser(mContentResolver, name, userHandle);
+        return Settings.Secure.getStringForUser(mContentResolver, name,
+                getRealUserHandle(userHandle));
     }
 
     @Override
@@ -52,14 +62,16 @@
 
     @Override
     public boolean putStringForUser(String name, String value, int userHandle) {
-        return Settings.Secure.putStringForUser(mContentResolver, name, value, userHandle);
+        return Settings.Secure.putStringForUser(mContentResolver, name, value,
+                getRealUserHandle(userHandle));
     }
 
     @Override
     public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
             int userHandle, boolean overrideableByRestore) {
         return Settings.Secure.putStringForUser(
-                mContentResolver, name, value, tag, makeDefault, userHandle, overrideableByRestore);
+                mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle),
+                overrideableByRestore);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
index 1bf5f07..b6846a3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
@@ -22,8 +22,11 @@
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.UserHandle;
 import android.provider.Settings;
 
+import com.android.systemui.settings.UserTracker;
+
 /**
  * Used to interact with Settings.Secure, Settings.Global, and Settings.System.
  *
@@ -46,6 +49,11 @@
     ContentResolver getContentResolver();
 
     /**
+     * Returns that {@link UserTracker} this instance was constructed with.
+     */
+    UserTracker getUserTracker();
+
+    /**
      * Returns the user id for the associated {@link ContentResolver}.
      */
     default int getUserId() {
@@ -53,6 +61,17 @@
     }
 
     /**
+     * Returns the actual current user handle when querying with the current user. Otherwise,
+     * returns the passed in user id.
+     */
+    default int getRealUserHandle(int userHandle) {
+        if (userHandle != UserHandle.USER_CURRENT) {
+            return userHandle;
+        }
+        return getUserTracker().getUserId();
+    }
+
+    /**
      * Construct the content URI for a particular name/value pair,
      * useful for monitoring changes with a ContentObserver.
      * @param name to look up in the table
@@ -84,18 +103,18 @@
      *
      * Implicitly calls {@link #getUriFor(String)} on the passed in name.
      */
-    default void registerContentObserver(String name, boolean notifyForDescendents,
+    default void registerContentObserver(String name, boolean notifyForDescendants,
             ContentObserver settingsObserver) {
-        registerContentObserver(getUriFor(name), notifyForDescendents, settingsObserver);
+        registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver);
     }
 
     /**
      * Convenience wrapper around
      * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
      */
-    default void registerContentObserver(Uri uri, boolean notifyForDescendents,
+    default void registerContentObserver(Uri uri, boolean notifyForDescendants,
             ContentObserver settingsObserver) {
-        registerContentObserverForUser(uri, notifyForDescendents, settingsObserver, getUserId());
+        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
     }
 
     /**
@@ -127,10 +146,10 @@
      * Implicitly calls {@link #getUriFor(String)} on the passed in name.
      */
     default void registerContentObserverForUser(
-            String name, boolean notifyForDescendents, ContentObserver settingsObserver,
+            String name, boolean notifyForDescendants, ContentObserver settingsObserver,
             int userHandle) {
         registerContentObserverForUser(
-                getUriFor(name), notifyForDescendents, settingsObserver, userHandle);
+                getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
     }
 
     /**
@@ -138,10 +157,10 @@
      * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
      */
     default void registerContentObserverForUser(
-            Uri uri, boolean notifyForDescendents, ContentObserver settingsObserver,
+            Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
             int userHandle) {
         getContentResolver().registerContentObserver(
-                uri, notifyForDescendents, settingsObserver, userHandle);
+                uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
     }
 
     /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
index 0b8257d..561495e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
@@ -19,7 +19,6 @@
 
 import android.annotation.UserIdInt
 import android.database.ContentObserver
-import android.os.UserHandle
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -29,8 +28,8 @@
 
     /** Returns a flow of [Unit] that is invoked each time that content is updated. */
     fun SettingsProxy.observerFlow(
+        @UserIdInt userId: Int,
         vararg names: String,
-        @UserIdInt userId: Int = UserHandle.USER_CURRENT,
     ): Flow<Unit> {
         return conflatedCallbackFlow {
             val observer =
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index 0dbb76f..fba7ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -20,14 +20,18 @@
 import android.net.Uri;
 import android.provider.Settings;
 
+import com.android.systemui.settings.UserTracker;
+
 import javax.inject.Inject;
 
 class SystemSettingsImpl implements SystemSettings {
     private final ContentResolver mContentResolver;
+    private final UserTracker mUserTracker;
 
     @Inject
-    SystemSettingsImpl(ContentResolver contentResolver) {
+    SystemSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
         mContentResolver = contentResolver;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -36,13 +40,19 @@
     }
 
     @Override
+    public UserTracker getUserTracker() {
+        return mUserTracker;
+    }
+
+    @Override
     public Uri getUriFor(String name) {
         return Settings.System.getUriFor(name);
     }
 
     @Override
     public String getStringForUser(String name, int userHandle) {
-        return Settings.System.getStringForUser(mContentResolver, name, userHandle);
+        return Settings.System.getStringForUser(mContentResolver, name,
+                getRealUserHandle(userHandle));
     }
 
     @Override
@@ -52,7 +62,8 @@
 
     @Override
     public boolean putStringForUser(String name, String value, int userHandle) {
-        return Settings.System.putStringForUser(mContentResolver, name, value, userHandle);
+        return Settings.System.putStringForUser(mContentResolver, name, value,
+                getRealUserHandle(userHandle));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index f71d988..a453726 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -25,6 +25,7 @@
 import android.provider.Settings;
 import android.view.WindowManager.LayoutParams;
 
+import com.android.internal.R;
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoMode;
@@ -55,7 +56,7 @@
     public static final String VOLUME_UP_SILENT = "sysui_volume_up_silent";
     public static final String VOLUME_SILENT_DO_NOT_DISTURB = "sysui_do_not_disturb";
 
-    public static final boolean DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT = false;
+    private final boolean mDefaultVolumeDownToEnterSilent;
     public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
     public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
 
@@ -72,12 +73,7 @@
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final ActivityStarter mActivityStarter;
     private VolumeDialog mDialog;
-    private VolumePolicy mVolumePolicy = new VolumePolicy(
-            DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT,  // volumeDownToEnterSilent
-            DEFAULT_VOLUME_UP_TO_EXIT_SILENT,  // volumeUpToExitSilent
-            DEFAULT_DO_NOT_DISTURB_WHEN_SILENT,  // doNotDisturbWhenSilent
-            400    // vibrateToSilentDebounce
-    );
+    private VolumePolicy mVolumePolicy;
 
     @Inject
     public VolumeDialogComponent(
@@ -107,7 +103,20 @@
                     mDialog = dialog;
                     mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
                 }).build();
+
+
+        mDefaultVolumeDownToEnterSilent = mContext.getResources()
+                .getBoolean(R.bool.config_volume_down_to_enter_silent);
+
+        mVolumePolicy = new VolumePolicy(
+                mDefaultVolumeDownToEnterSilent,  // volumeDownToEnterSilent
+                DEFAULT_VOLUME_UP_TO_EXIT_SILENT,  // volumeUpToExitSilent
+                DEFAULT_DO_NOT_DISTURB_WHEN_SILENT,  // doNotDisturbWhenSilent
+                400    // vibrateToSilentDebounce
+        );
+
         applyConfiguration();
+
         tunerService.addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
                 VOLUME_SILENT_DO_NOT_DISTURB);
         demoModeController.addCallback(this);
@@ -121,7 +130,7 @@
 
         if (VOLUME_DOWN_SILENT.equals(key)) {
             volumeDownToEnterSilent =
-                TunerService.parseIntegerSwitch(newValue, DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT);
+                TunerService.parseIntegerSwitch(newValue, mDefaultVolumeDownToEnterSilent);
         } else if (VOLUME_UP_SILENT.equals(key)) {
             volumeUpToExitSilent =
                 TunerService.parseIntegerSwitch(newValue, DEFAULT_VOLUME_UP_TO_EXIT_SILENT);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 98d904e..89b66ee 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -46,7 +46,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.service.notification.Condition;
@@ -69,6 +68,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
@@ -133,6 +133,7 @@
     private final CaptioningManager mCaptioningManager;
     private final KeyguardManager mKeyguardManager;
     private final ActivityManager mActivityManager;
+    private final UserTracker mUserTracker;
     protected C mCallbacks = new C();
     private final State mState = new State();
     protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
@@ -180,6 +181,7 @@
             CaptioningManager captioningManager,
             KeyguardManager keyguardManager,
             ActivityManager activityManager,
+            UserTracker userTracker,
             DumpManager dumpManager
     ) {
         mContext = context.getApplicationContext();
@@ -209,6 +211,7 @@
         mCaptioningManager = captioningManager;
         mKeyguardManager = keyguardManager;
         mActivityManager = activityManager;
+        mUserTracker = userTracker;
         dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
 
         boolean accessibilityVolumeStreamActive = accessibilityManager
@@ -371,7 +374,7 @@
         if (System.currentTimeMillis() - mLastToggledRingerOn < TOUCH_FEEDBACK_TIMEOUT_MS) {
             try {
                 mAudioService.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD,
-                        UserHandle.USER_CURRENT);
+                        mUserTracker.getUserId());
             } catch (RemoteException e) {
                 // ignore
             }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 4cbc709..4da5d49 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -273,7 +273,7 @@
             };
 
             mSecureSettings.registerContentObserverForUser(
-                    Settings.Secure.getUriFor(Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT),
+                    Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
                     false /* notifyForDescendants */,
                     mDefaultPaymentAppObserver,
                     UserHandle.USER_ALL);
@@ -293,7 +293,7 @@
             };
 
             mSecureSettings.registerContentObserverForUser(
-                    Settings.Secure.getUriFor(QuickAccessWalletClientImpl.SETTING_KEY),
+                    QuickAccessWalletClientImpl.SETTING_KEY,
                     false /* notifyForDescendants */,
                     mWalletPreferenceObserver,
                     UserHandle.USER_ALL);
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
new file mode 100644
index 0000000..7b8235a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.systemui.wallet.controller
+
+import android.Manifest
+import android.content.Context
+import android.content.IntentFilter
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.shareIn
+
+@SysUISingleton
+class WalletContextualSuggestionsController
+@Inject
+constructor(
+    @Application private val applicationCoroutineScope: CoroutineScope,
+    private val walletController: QuickAccessWalletController,
+    broadcastDispatcher: BroadcastDispatcher,
+    featureFlags: FeatureFlags
+) {
+    private val allWalletCards: Flow<List<WalletCard>> =
+        if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+            conflatedCallbackFlow {
+                val callback =
+                    object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+                        override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
+                            trySendWithFailureLogging(response.walletCards, TAG)
+                        }
+
+                        override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
+                            trySendWithFailureLogging(emptyList<WalletCard>(), TAG)
+                        }
+                    }
+
+                walletController.setupWalletChangeObservers(
+                    callback,
+                    QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+                    QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+                )
+                walletController.updateWalletPreference()
+                walletController.queryWalletCards(callback)
+
+                awaitClose {
+                    walletController.unregisterWalletChangeObservers(
+                        QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+                        QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+                    )
+                }
+            }
+        } else {
+            emptyFlow()
+        }
+
+    private val contextualSuggestionsCardIds: Flow<Set<String>> =
+        if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+            broadcastDispatcher.broadcastFlow(
+                filter = IntentFilter(ACTION_UPDATE_WALLET_CONTEXTUAL_SUGGESTIONS),
+                permission = Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE,
+                flags = Context.RECEIVER_EXPORTED
+            ) { intent, _ ->
+                if (intent.hasExtra(UPDATE_CARD_IDS_EXTRA)) {
+                    intent.getStringArrayListExtra(UPDATE_CARD_IDS_EXTRA).toSet()
+                } else {
+                    emptySet()
+                }
+            }
+        } else {
+            emptyFlow()
+        }
+
+    val contextualSuggestionCards: Flow<List<WalletCard>> =
+        combine(allWalletCards, contextualSuggestionsCardIds) { cards, ids ->
+                cards.filter { card -> ids.contains(card.cardId) }
+            }
+            .shareIn(applicationCoroutineScope, replay = 1, started = SharingStarted.Eagerly)
+
+    companion object {
+        private const val ACTION_UPDATE_WALLET_CONTEXTUAL_SUGGESTIONS =
+            "com.android.systemui.wallet.UPDATE_CONTEXTUAL_SUGGESTIONS"
+
+        private const val UPDATE_CARD_IDS_EXTRA = "cardIds"
+
+        private const val TAG = "WalletSuggestions"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 1f1b32c..8b925b7 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -26,7 +26,6 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.Trace;
-import android.os.UserHandle;
 import android.service.wallpaper.WallpaperService;
 import android.util.Log;
 import android.view.Surface;
@@ -37,6 +36,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.io.FileDescriptor;
@@ -58,6 +58,8 @@
     private volatile int mPages = 1;
     private boolean mPagesComputed = false;
 
+    private final UserTracker mUserTracker;
+
     // used for most tasks (call canvas.drawBitmap, load/unload the bitmap)
     @Background
     private final DelayableExecutor mBackgroundExecutor;
@@ -66,9 +68,11 @@
     private static final int DELAY_UNLOAD_BITMAP = 2000;
 
     @Inject
-    public ImageWallpaper(@Background DelayableExecutor backgroundExecutor) {
+    public ImageWallpaper(@Background DelayableExecutor backgroundExecutor,
+            UserTracker userTracker) {
         super();
         mBackgroundExecutor = backgroundExecutor;
+        mUserTracker = userTracker;
     }
 
     @Override
@@ -288,7 +292,7 @@
             boolean loadSuccess = false;
             Bitmap bitmap;
             try {
-                bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
+                bitmap = mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
                 if (bitmap != null
                         && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
                     throw new RuntimeException("Wallpaper is too large to draw!");
@@ -300,9 +304,9 @@
                 // default wallpaper can't be loaded.
                 Log.w(TAG, "Unable to load wallpaper!", exception);
                 mWallpaperManager.clearWallpaper(
-                        WallpaperManager.FLAG_SYSTEM, UserHandle.USER_CURRENT);
+                        WallpaperManager.FLAG_SYSTEM, mUserTracker.getUserId());
                 try {
-                    bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
+                    bitmap = mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
                 } catch (RuntimeException | OutOfMemoryError e) {
                     Log.w(TAG, "Unable to load default wallpaper!", e);
                     bitmap = null;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 7033ccd..5d896cb 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -236,7 +236,8 @@
 
         // Store callback in a field so it won't get GC'd
         mStatusBarWindowCallback =
-                (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded) ->
+                (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded,
+                        isDreaming) ->
                         mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
         notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
 
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 8ef98f0..bd60401 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.wmshell;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
@@ -50,6 +48,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.notetask.NoteTaskInitializer;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.tracing.ProtoTraceable;
 import com.android.systemui.statusbar.CommandQueue;
@@ -124,6 +123,7 @@
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final ProtoTracer mProtoTracer;
     private final UserTracker mUserTracker;
+    private final DisplayTracker mDisplayTracker;
     private final NoteTaskInitializer mNoteTaskInitializer;
     private final Executor mSysUiMainExecutor;
 
@@ -186,6 +186,7 @@
             ProtoTracer protoTracer,
             WakefulnessLifecycle wakefulnessLifecycle,
             UserTracker userTracker,
+            DisplayTracker displayTracker,
             NoteTaskInitializer noteTaskInitializer,
             @Main Executor sysUiMainExecutor) {
         mContext = context;
@@ -203,6 +204,7 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mProtoTracer = protoTracer;
         mUserTracker = userTracker;
+        mDisplayTracker = displayTracker;
         mNoteTaskInitializer = noteTaskInitializer;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
@@ -268,7 +270,7 @@
             public void onStartTransition(boolean isEntering) {
                 mSysUiMainExecutor.execute(() -> {
                     mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
-                            true).commitUpdate(DEFAULT_DISPLAY);
+                            true).commitUpdate(mDisplayTracker.getDefaultDisplayId());
                 });
             }
 
@@ -276,7 +278,7 @@
             public void onStartFinished(Rect bounds) {
                 mSysUiMainExecutor.execute(() -> {
                     mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
-                            true).commitUpdate(DEFAULT_DISPLAY);
+                            true).commitUpdate(mDisplayTracker.getDefaultDisplayId());
                 });
             }
 
@@ -284,7 +286,7 @@
             public void onStopFinished(Rect bounds) {
                 mSysUiMainExecutor.execute(() -> {
                     mSysUiState.setFlag(SYSUI_STATE_ONE_HANDED_ACTIVE,
-                            false).commitUpdate(DEFAULT_DISPLAY);
+                            false).commitUpdate(mDisplayTracker.getDefaultDisplayId());
                 });
             }
         });
@@ -333,7 +335,8 @@
             @Override
             public void setImeWindowStatus(int displayId, IBinder token, int vis,
                     int backDisposition, boolean showImeSwitcher) {
-                if (displayId == DEFAULT_DISPLAY && (vis & InputMethodService.IME_VISIBLE) != 0) {
+                if (displayId == mDisplayTracker.getDefaultDisplayId()
+                        && (vis & InputMethodService.IME_VISIBLE) != 0) {
                     oneHanded.stopOneHanded(
                             OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_POP_IME_OUT);
                 }
@@ -346,7 +349,7 @@
             @Override
             public void onVisibilityChanged(boolean hasFreeformTasks) {
                 mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks)
-                        .commitUpdate(DEFAULT_DISPLAY);
+                        .commitUpdate(mDisplayTracker.getDefaultDisplayId());
             }
         }, mSysUiMainExecutor);
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index c76b127..43a2017 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -15,7 +15,6 @@
  */
 package com.android.keyguard
 
-import com.android.systemui.statusbar.CommandQueue
 import android.content.BroadcastReceiver
 import android.testing.AndroidTestingRunner
 import android.view.View
@@ -33,16 +32,17 @@
 import com.android.systemui.plugins.ClockEvents
 import com.android.systemui.plugins.ClockFaceController
 import com.android.systemui.plugins.ClockFaceEvents
+import com.android.systemui.plugins.ClockTickRate
 import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
-import java.util.TimeZone
-import java.util.concurrent.Executor
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.yield
@@ -51,15 +51,16 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import java.util.*
+import java.util.concurrent.Executor
+import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -73,7 +74,7 @@
     @Mock private lateinit var animations: ClockAnimations
     @Mock private lateinit var events: ClockEvents
     @Mock private lateinit var clock: ClockController
-    @Mock private lateinit var mainExecutor: Executor
+    @Mock private lateinit var mainExecutor: DelayableExecutor
     @Mock private lateinit var bgExecutor: Executor
     @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var smallClockController: ClockFaceController
@@ -98,6 +99,8 @@
         whenever(largeClockController.events).thenReturn(largeClockEvents)
         whenever(clock.events).thenReturn(events)
         whenever(clock.animations).thenReturn(animations)
+        whenever(smallClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
+        whenever(largeClockEvents.tickRate).thenReturn(ClockTickRate.PER_MINUTE)
 
         repository = FakeKeyguardRepository()
 
@@ -140,8 +143,9 @@
 
     @Test
     fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
-        verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
-        verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
+        // TODO(b/266103601): delete this test and add more coverage for updateColors()
+        // verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
+        // verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
 
         val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
         verify(configurationController).addCallback(capture(captor))
@@ -152,9 +156,6 @@
 
     @Test
     fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
-        verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
-        verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
-
         val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
         verify(configurationController).addCallback(capture(captor))
         captor.value.onDensityOrFontScaleChanged()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 1059543..50645e5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -150,4 +150,11 @@
                 getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
                 false);
     }
+
+
+    @Test
+    public void testReset() {
+        mKeyguardAbsKeyInputViewController.reset();
+        verify(mKeyguardMessageAreaController).setMessage("", false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 9a9acf3..36b3f89 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -29,11 +29,11 @@
 
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.net.Uri;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
@@ -48,6 +48,7 @@
 import com.android.systemui.plugins.ClockController;
 import com.android.systemui.plugins.ClockEvents;
 import com.android.systemui.plugins.ClockFaceController;
+import com.android.systemui.plugins.ClockFaceEvents;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -100,6 +101,8 @@
     @Mock
     private ClockEvents mClockEvents;
     @Mock
+    private ClockFaceEvents mClockFaceEvents;
+    @Mock
     DumpManager mDumpManager;
     @Mock
     ClockEventController mClockEventController;
@@ -119,6 +122,11 @@
     @Mock
     private LogBuffer mLogBuffer;
 
+    private final View mFakeDateView = (View) (new ViewGroup(mContext) {
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {}
+    });
+    private final View mFakeWeatherView = new View(mContext);
     private final View mFakeSmartspaceView = new View(mContext);
 
     private KeyguardClockSwitchController mController;
@@ -146,6 +154,8 @@
         when(mLargeClockView.getContext()).thenReturn(getContext());
 
         when(mView.isAttachedToWindow()).thenReturn(true);
+        when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
+        when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
         mExecutor = new FakeExecutor(new FakeSystemClock());
         mController = new KeyguardClockSwitchController(
@@ -169,6 +179,8 @@
         when(mClockController.getLargeClock()).thenReturn(mLargeClockController);
         when(mClockController.getSmallClock()).thenReturn(mSmallClockController);
         when(mClockController.getEvents()).thenReturn(mClockEvents);
+        when(mSmallClockController.getEvents()).thenReturn(mClockFaceEvents);
+        when(mLargeClockController.getEvents()).thenReturn(mClockFaceEvents);
         when(mClockController.getAnimations()).thenReturn(mClockAnimations);
         when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
         when(mClockEventController.getClock()).thenReturn(mClockController);
@@ -253,6 +265,19 @@
     }
 
     @Test
+    public void onLocaleListChanged_rebuildsSmartspaceViews_whenDecouplingEnabled() {
+        when(mSmartspaceController.isEnabled()).thenReturn(true);
+        when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+        mController.init();
+
+        mController.onLocaleListChanged();
+        // Should be called once on initial setup, then once again for locale change
+        verify(mSmartspaceController, times(2)).buildAndConnectDateView(mView);
+        verify(mSmartspaceController, times(2)).buildAndConnectWeatherView(mView);
+        verify(mSmartspaceController, times(2)).buildAndConnectView(mView);
+    }
+
+    @Test
     public void testSmartspaceDisabledShowsKeyguardStatusArea() {
         when(mSmartspaceController.isEnabled()).thenReturn(false);
         mController.init();
@@ -275,7 +300,7 @@
         ArgumentCaptor<ContentObserver> observerCaptor =
                 ArgumentCaptor.forClass(ContentObserver.class);
         mController.init();
-        verify(mSecureSettings).registerContentObserverForUser(any(Uri.class),
+        verify(mSecureSettings).registerContentObserverForUser(any(String.class),
                 anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
         ContentObserver observer = observerCaptor.getValue();
         mExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index 01365b4..1a365ef 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -25,9 +25,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -39,6 +37,7 @@
 import com.android.keyguard.dagger.KeyguardStatusViewComponent;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.settings.FakeDisplayTracker;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,12 +57,12 @@
     @Mock
     private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     @Mock
-    private DisplayManager mDisplayManager;
-    @Mock
     private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
 
+    private Executor mMainExecutor = Runnable::run;
     private Executor mBackgroundExecutor = Runnable::run;
     private KeyguardDisplayManager mManager;
+    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
     // The default and secondary displays are both in the default group
     private Display mDefaultDisplay;
@@ -75,9 +74,9 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
         mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
-                mKeyguardStatusViewComponentFactory, mBackgroundExecutor));
+                mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor,
+                mBackgroundExecutor));
         doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
 
         mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
@@ -96,23 +95,21 @@
 
     @Test
     public void testShow_defaultDisplayOnly() {
-        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay});
+        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay});
         mManager.show();
         verify(mManager, never()).createPresentation(any());
     }
 
     @Test
     public void testShow_includeSecondaryDisplay() {
-        when(mDisplayManager.getDisplays()).thenReturn(
-                new Display[]{mDefaultDisplay, mSecondaryDisplay});
+        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
         mManager.show();
         verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
     }
 
     @Test
     public void testShow_includeAlwaysUnlockedDisplay() {
-        when(mDisplayManager.getDisplays()).thenReturn(
-                new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay});
+        mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mAlwaysUnlockedDisplay});
 
         mManager.show();
         verify(mManager, never()).createPresentation(any());
@@ -120,9 +117,8 @@
 
     @Test
     public void testShow_includeSecondaryAndAlwaysUnlockedDisplays() {
-        when(mDisplayManager.getDisplays()).thenReturn(
+        mDisplayTracker.setAllDisplays(
                 new Display[]{mDefaultDisplay, mSecondaryDisplay, mAlwaysUnlockedDisplay});
-
         mManager.show();
         verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 84f6d91..075ef9d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -405,6 +405,13 @@
     }
 
     @Test
+    public void onBouncerVisibilityChanged_resetsScale() {
+        mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE);
+
+        verify(mView).resetScale();
+    }
+
+    @Test
     public void onStartingToHide_sideFpsHintShown_sideFpsHintHidden() {
         setupGetSecurityView();
         setupConditionsToEnableSideFpsHint();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 36ed669..1bbc199 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -49,6 +49,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 
 import androidx.constraintlayout.widget.ConstraintSet;
 import androidx.test.filters.SmallTest;
@@ -357,6 +359,27 @@
         assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
     }
 
+    @Test
+    public void testPlayBackAnimation() {
+        OnBackAnimationCallback backCallback = mKeyguardSecurityContainer.getBackCallback();
+        backCallback.onBackStarted(createBackEvent(0, 0));
+        mKeyguardSecurityContainer.getBackCallback().onBackProgressed(
+                createBackEvent(0, 1));
+        assertThat(mKeyguardSecurityContainer.getScaleX()).isEqualTo(
+                KeyguardSecurityContainer.MIN_BACK_SCALE);
+        assertThat(mKeyguardSecurityContainer.getScaleY()).isEqualTo(
+                KeyguardSecurityContainer.MIN_BACK_SCALE);
+
+        // reset scale
+        mKeyguardSecurityContainer.resetScale();
+        assertThat(mKeyguardSecurityContainer.getScaleX()).isEqualTo(1);
+        assertThat(mKeyguardSecurityContainer.getScaleY()).isEqualTo(1);
+    }
+
+    private BackEvent createBackEvent(float touchX, float progress) {
+        return new BackEvent(0, 0, progress, BackEvent.EDGE_LEFT);
+    }
+
     private Configuration configuration(@Configuration.Orientation int orientation) {
         Configuration config = new Configuration();
         config.orientation = orientation;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index 06082b6..68dc6c0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -29,6 +29,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
 
@@ -52,6 +53,7 @@
     private ConfigurationController mConfigurationController;
     @Mock
     private ActivityStarter mActivityStarter;
+    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private DumpManager mDumpManager = new DumpManager();
 
     private KeyguardSliceViewController mController;
@@ -63,7 +65,7 @@
         when(mView.getContext()).thenReturn(mContext);
         mController = new KeyguardSliceViewController(
                 mView, mActivityStarter, mConfigurationController,
-                mTunerService, mDumpManager);
+                mTunerService, mDumpManager, mDisplayTracker);
         mController.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index df6752a..d1650b7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -17,7 +17,6 @@
 package com.android.keyguard;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
@@ -41,7 +40,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -95,7 +93,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.Settings;
 import android.service.dreams.IDreamManager;
 import android.service.trust.TrustAgentService;
 import android.telephony.ServiceState;
@@ -238,8 +235,6 @@
     @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
-    private PowerManager mPowerManager;
-    @Mock
     private GlobalSettings mGlobalSettings;
     private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
     @Mock
@@ -834,6 +829,19 @@
     }
 
     @Test
+    public void doesNotTryToAuthenticateWhenKeyguardIsNotShowingButOccluded_whenAssistant() {
+        mKeyguardUpdateMonitor.setKeyguardShowing(false, true);
+        mKeyguardUpdateMonitor.setAssistantVisible(true);
+
+        verify(mFaceManager, never()).authenticate(any(),
+                any(),
+                any(),
+                any(),
+                anyInt(),
+                anyBoolean());
+    }
+
+    @Test
     public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
@@ -846,6 +854,32 @@
     }
 
     @Test
+    public void faceUnlockDoesNotRunWhenDeviceIsGoingToSleepWithAssistantVisible() {
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+        mKeyguardUpdateMonitor.setAssistantVisible(true);
+
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+        mTestableLooper.processAllMessages();
+        clearInvocations(mFaceManager);
+
+        // Device going to sleep while assistant is visible
+        mKeyguardUpdateMonitor.handleStartedGoingToSleep(0);
+        mKeyguardUpdateMonitor.handleFinishedGoingToSleep(0);
+        mTestableLooper.moveTimeForward(DEFAULT_CANCEL_SIGNAL_TIMEOUT);
+        mTestableLooper.processAllMessages();
+
+        mKeyguardUpdateMonitor.handleKeyguardReset();
+
+        assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse();
+        verify(mFaceManager, never()).authenticate(any(),
+                any(),
+                any(),
+                any(),
+                anyInt(),
+                anyBoolean());
+    }
+
+    @Test
     public void testIgnoresAuth_whenTrustAgentOnKeyguard_withoutBypass() {
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mTestableLooper.processAllMessages();
@@ -1872,28 +1906,6 @@
     }
 
     @Test
-    public void testFingerAcquired_wakesUpPowerManager() {
-        cleanupKeyguardUpdateMonitor();
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.kg_wake_on_acquire_start, true);
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
-        fingerprintAcquireStart();
-
-        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
-    }
-
-    @Test
-    public void testFingerAcquired_doesNotWakeUpPowerManager() {
-        cleanupKeyguardUpdateMonitor();
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.kg_wake_on_acquire_start, false);
-        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
-        fingerprintAcquireStart();
-
-        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
-    }
-
-    @Test
     public void testDreamingStopped_faceDoesNotRun() {
         mKeyguardUpdateMonitor.dispatchDreamingStopped();
         mTestableLooper.processAllMessages();
@@ -2374,11 +2386,6 @@
                 .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
     }
 
-    private void fingerprintAcquireStart() {
-        mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
-                .onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START);
-    }
-
     private void deviceInPostureStateOpened() {
         mKeyguardUpdateMonitor.mPostureCallback.onPostureChanged(DEVICE_POSTURE_OPENED);
     }
@@ -2525,7 +2532,7 @@
                     mAuthController, mTelephonyListenerManager,
                     mInteractionJankMonitor, mLatencyTracker, mActiveUnlockConfig,
                     mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
-                    mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
+                    mTrustManager, mSubscriptionManager, mUserManager,
                     mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
                     mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
index 81d0034..32edf8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
@@ -148,7 +148,7 @@
 
         // Act
         whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
-        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
 
         // Assert
         verify(mockPackageManager, times(2)).setComponentEnabledSetting(
@@ -175,7 +175,7 @@
 
         // Act
         whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
-        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
+        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
 
         // Assert
         verify(mockPackageManager, times(2)).setComponentEnabledSetting(
@@ -198,13 +198,13 @@
 
         // Act
         whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
-        flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id + 1))
+        flagListener.value.onFlagChanged(TestFlagEvent("other flag"))
 
         // Assert
         verifyZeroInteractions(mockPackageManager)
     }
 
-    private class TestFlagEvent(override val flagId: Int) : FlagListenable.FlagEvent {
+    private class TestFlagEvent(override val flagName: String) : FlagListenable.FlagEvent {
         override fun requestNoRestart() {}
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 0627fc6..4cf5a4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -33,8 +34,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -88,7 +91,9 @@
 import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.log.ScreenDecorationsLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
@@ -101,8 +106,12 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -116,7 +125,8 @@
     private WindowManager mWindowManager;
     private DisplayManager mDisplayManager;
     private SecureSettings mSecureSettings;
-    private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+    private FakeExecutor mExecutor;
+    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private FakeThreadFactory mThreadFactory;
     private ArrayList<DecorProvider> mPrivacyDecorProviders;
     private ArrayList<DecorProvider> mFaceScanningProviders;
@@ -157,6 +167,8 @@
     private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener;
     @Mock
     private CutoutDecorProviderFactory mCutoutFactory;
+    @Captor
+    private ArgumentCaptor<AuthController.Callback> mAuthControllerCallback;
     private List<DecorProvider> mMockCutoutList;
 
     @Before
@@ -165,6 +177,7 @@
 
         Handler mainHandler = new Handler(TestableLooper.get(this).getLooper());
         mSecureSettings = new FakeSettings();
+        mExecutor = new FakeExecutor(new FakeSystemClock());
         mThreadFactory = new FakeThreadFactory(mExecutor);
         mThreadFactory.setHandler(mainHandler);
 
@@ -217,11 +230,14 @@
                 mAuthController,
                 mStatusBarStateController,
                 mKeyguardUpdateMonitor,
-                mExecutor));
+                mExecutor,
+                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
 
         mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
-                mTunerService, mUserTracker, mDotViewController, mThreadFactory,
-                mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) {
+                mTunerService, mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory,
+                mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
+                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+                mAuthController) {
             @Override
             public void start() {
                 super.start();
@@ -1159,6 +1175,44 @@
     }
 
     @Test
+    public void faceSensorLocationChangesReloadsFaceScanningOverlay() {
+        mFaceScanningProviders = new ArrayList<>();
+        mFaceScanningProviders.add(mFaceScanningDecorProvider);
+        when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
+        when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
+        ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor,
+                mSecureSettings, mTunerService, mUserTracker, mDisplayTracker, mDotViewController,
+                mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
+                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController);
+        screenDecorations.start();
+        verify(mAuthController).addCallback(mAuthControllerCallback.capture());
+        when(mContext.getDisplay()).thenReturn(mDisplay);
+        when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+                DisplayInfo displayInfo = invocation.getArgument(0);
+                int modeId = 1;
+                displayInfo.modeId = modeId;
+                displayInfo.supportedModes = new Display.Mode[]{new Display.Mode(modeId, 1024, 1024,
+                        90)};
+                return false;
+            }
+        });
+        mExecutor.runAllReady();
+        clearInvocations(mFaceScanningDecorProvider);
+
+        AuthController.Callback callback = mAuthControllerCallback.getValue();
+        callback.onFaceSensorLocationChanged();
+        mExecutor.runAllReady();
+
+        verify(mFaceScanningDecorProvider).onReloadResAndMeasure(any(),
+                anyInt(),
+                anyInt(),
+                anyInt(),
+                any());
+    }
+
+    @Test
     public void testPrivacyDotShowingListenerWorkWellWithNullParameter() {
         mPrivacyDotShowingListener.onPrivacyDotShown(null);
         mPrivacyDotShowingListener.onPrivacyDotHidden(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
index 7aa4763..4a5c1be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonModeObserverTest.java
@@ -22,14 +22,16 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
-import android.os.UserHandle;
+import android.app.ActivityManager;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -42,11 +44,14 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class AccessibilityButtonModeObserverTest extends SysuiTestCase {
+    private static final int MY_USER_ID = ActivityManager.getCurrentUser();
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private AccessibilityButtonModeObserver.ModeChangedListener mListener;
 
     private AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
@@ -56,10 +61,12 @@
 
     @Before
     public void setUp() {
+        when(mUserTracker.getUserId()).thenReturn(MY_USER_ID);
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
-        mAccessibilityButtonModeObserver = new AccessibilityButtonModeObserver(mContext);
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, MY_USER_ID);
+        mAccessibilityButtonModeObserver = new AccessibilityButtonModeObserver(mContext,
+                mUserTracker);
     }
 
     @Test
@@ -67,7 +74,7 @@
         mAccessibilityButtonModeObserver.addListener(mListener);
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE,
-                UserHandle.USER_CURRENT);
+                MY_USER_ID);
 
         mAccessibilityButtonModeObserver.mContentObserver.onChange(false);
 
@@ -80,7 +87,7 @@
         mAccessibilityButtonModeObserver.removeListener(mListener);
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE,
-                UserHandle.USER_CURRENT);
+                MY_USER_ID);
 
         mAccessibilityButtonModeObserver.mContentObserver.onChange(false);
 
@@ -91,7 +98,7 @@
     public void getCurrentAccessibilityButtonMode_expectedValue() {
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, TEST_A11Y_BTN_MODE_VALUE,
-                UserHandle.USER_CURRENT);
+                MY_USER_ID);
 
         final int actualValue =
                 mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
index 4145437..a5a7a4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java
@@ -21,14 +21,16 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
-import android.os.UserHandle;
+import android.app.ActivityManager;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -42,11 +44,14 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class AccessibilityButtonTargetsObserverTest extends SysuiTestCase {
+    private static final int MY_USER_ID = ActivityManager.getCurrentUser();
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private AccessibilityButtonTargetsObserver.TargetsChangedListener mListener;
 
     private AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
@@ -55,7 +60,9 @@
 
     @Before
     public void setUp() {
-        mAccessibilityButtonTargetsObserver = new AccessibilityButtonTargetsObserver(mContext);
+        when(mUserTracker.getUserId()).thenReturn(MY_USER_ID);
+        mAccessibilityButtonTargetsObserver = new AccessibilityButtonTargetsObserver(mContext,
+                mUserTracker);
     }
 
     @Test
@@ -63,7 +70,7 @@
         mAccessibilityButtonTargetsObserver.addListener(mListener);
         Settings.Secure.putStringForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
-                UserHandle.USER_CURRENT);
+                MY_USER_ID);
 
         mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false);
 
@@ -76,7 +83,7 @@
         mAccessibilityButtonTargetsObserver.removeListener(mListener);
         Settings.Secure.putStringForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
-                UserHandle.USER_CURRENT);
+                MY_USER_ID);
 
         mAccessibilityButtonTargetsObserver.mContentObserver.onChange(false);
 
@@ -87,7 +94,7 @@
     public void getCurrentAccessibilityButtonTargets_expectedValue() {
         Settings.Secure.putStringForUser(mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
-                UserHandle.USER_CURRENT);
+                MY_USER_ID);
 
         final String actualValue =
                 mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index 58b4af4..da419d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.CommandQueue;
 
 import org.junit.Before;
@@ -76,6 +77,7 @@
 
     private IWindowMagnificationConnection mIWindowMagnificationConnection;
     private WindowMagnification mWindowMagnification;
+    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
     @Before
     public void setUp() throws Exception {
@@ -88,7 +90,7 @@
                 any(IWindowMagnificationConnection.class));
         mWindowMagnification = new WindowMagnification(getContext(),
                 getContext().getMainThreadHandler(), mCommandQueue,
-                mModeSwitchesController, mSysUiState, mOverviewProxyService);
+                mModeSwitchesController, mSysUiState, mOverviewProxyService, mDisplayTracker);
         mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
                 mContext.getSystemService(DisplayManager.class));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
index 41fd2b3..9c601a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SecureSettingsContentObserverTest.java
@@ -18,18 +18,20 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.ActivityManager;
 import android.content.Context;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 /** Test for {@link SecureSettingsContentObserver}. */
 @RunWith(AndroidTestingRunner.class)
@@ -40,7 +42,9 @@
 
     @Before
     public void setUpObserver() {
-        mTestObserver = new FakeSecureSettingsContentObserver(mContext,
+        UserTracker userTracker = Mockito.mock(UserTracker.class);
+        Mockito.when(userTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+        mTestObserver = new FakeSecureSettingsContentObserver(mContext, userTracker,
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE);
     }
 
@@ -57,7 +61,7 @@
     @Test
     public void checkValue() {
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 1, UserHandle.USER_CURRENT);
+                Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 1, ActivityManager.getCurrentUser());
 
         assertThat(mTestObserver.getSettingsValue()).isEqualTo("1");
     }
@@ -66,9 +70,9 @@
     private static class FakeSecureSettingsContentObserver extends
             SecureSettingsContentObserver<Object> {
 
-        protected FakeSecureSettingsContentObserver(Context context,
+        protected FakeSecureSettingsContentObserver(Context context, UserTracker userTracker,
                 String secureSettingsKey) {
-            super(context, secureSettingsKey);
+            super(context, userTracker, secureSettingsKey);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index b7d3459..f4505f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -32,8 +32,11 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.AdditionalAnswers.returnsSecondArg;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
@@ -75,7 +78,9 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.util.leak.ReferenceTestUtils;
+import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.utils.os.FakeHandler;
 
 import org.junit.After;
@@ -111,14 +116,17 @@
     IRemoteMagnificationAnimationCallback mAnimationCallback;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+    @Mock
+    private SecureSettings mSecureSettings;
 
     private Handler mHandler;
     private TestableWindowManager mWindowManager;
-    private SysUiState mSysUiState = new SysUiState();
+    private SysUiState mSysUiState;
     private Resources mResources;
     private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
     private WindowMagnificationController mWindowMagnificationController;
     private Instrumentation mInstrumentation;
+    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
 
     @Before
@@ -137,7 +145,12 @@
             return null;
         }).when(mSfVsyncFrameProvider).postFrameCallback(
                 any(FrameCallback.class));
+        mSysUiState = new SysUiState(mDisplayTracker);
         mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
+        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
+                returnsSecondArg());
+        when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
+                returnsSecondArg());
 
         mResources = getContext().getOrCreateTestableResources().getResources();
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index ccf2f8b..f75dc03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.CommandQueue;
 
 import org.junit.Before;
@@ -74,6 +75,8 @@
     private CommandQueue mCommandQueue;
     private WindowMagnification mWindowMagnification;
     private OverviewProxyListener mOverviewProxyListener;
+    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -87,10 +90,10 @@
 
         when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
 
-        mCommandQueue = new CommandQueue(getContext());
+        mCommandQueue = new CommandQueue(getContext(), new FakeDisplayTracker(getContext()));
         mWindowMagnification = new WindowMagnification(getContext(),
                 getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
-                mSysUiState, mOverviewProxyService);
+                mSysUiState, mOverviewProxyService, mDisplayTracker);
         mWindowMagnification.start();
 
         final ArgumentCaptor<OverviewProxyListener> listenerArgumentCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 8ca17b9..f34a36f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.os.UserHandle;
@@ -40,6 +41,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 import org.junit.Before;
@@ -48,6 +50,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -71,8 +75,12 @@
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
     private KeyguardUpdateMonitorCallback mKeyguardCallback;
 
+    @Mock
+    private SecureSettings mSecureSettings;
+
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mContextWrapper = new ContextWrapper(mContext) {
             @Override
             public Context createContextAsUser(UserHandle user, int flags) {
@@ -128,7 +136,7 @@
     public void onKeyguardVisibilityChanged_showing_destroyWidget() {
         enableAccessibilityFloatingMenuConfig();
         mController = setUpController();
-        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper);
+        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
         captureKeyguardUpdateMonitorCallback();
         mKeyguardCallback.onUserUnlocked();
 
@@ -154,7 +162,7 @@
         final int fakeUserId = 1;
         enableAccessibilityFloatingMenuConfig();
         mController = setUpController();
-        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper);
+        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
         captureKeyguardUpdateMonitorCallback();
 
         mKeyguardCallback.onUserSwitching(fakeUserId);
@@ -167,7 +175,7 @@
         final int fakeUserId = 1;
         enableAccessibilityFloatingMenuConfig();
         mController = setUpController();
-        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper);
+        mController.mFloatingMenu = new AccessibilityFloatingMenu(mContextWrapper, mSecureSettings);
         captureKeyguardUpdateMonitorCallback();
         mKeyguardCallback.onUserUnlocked();
         mKeyguardCallback.onKeyguardVisibilityChanged(true);
@@ -197,7 +205,7 @@
     public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() {
         Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
-                UserHandle.USER_CURRENT);
+                ActivityManager.getCurrentUser());
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -208,7 +216,7 @@
     @Test
     public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() {
         Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -220,7 +228,7 @@
     public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_destroyWidget() {
         Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
-                UserHandle.USER_CURRENT);
+                ActivityManager.getCurrentUser());
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -231,7 +239,7 @@
     @Test
     public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() {
         Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
         mController = setUpController();
 
         mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -243,7 +251,7 @@
     public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() {
         Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
-                UserHandle.USER_CURRENT);
+                ActivityManager.getCurrentUser());
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -255,7 +263,7 @@
     public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() {
         Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
-                UserHandle.USER_CURRENT);
+                ActivityManager.getCurrentUser());
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged("");
@@ -267,7 +275,7 @@
     public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_destroyWidget() {
         Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
+                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -279,7 +287,7 @@
     public void onAccessibilityButtonTargetsChanged_navBarModeAndNoButtonTargets_destroyWidget() {
         Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
-                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT);
+                ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
         mController = setUpController();
 
         mController.onAccessibilityButtonTargetsChanged("");
@@ -293,7 +301,7 @@
         mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         final AccessibilityFloatingMenuController controller =
                 new AccessibilityFloatingMenuController(mContextWrapper, mTargetsObserver,
-                        mModeObserver, mKeyguardUpdateMonitor);
+                        mModeObserver, mKeyguardUpdateMonitor, mSecureSettings);
         controller.init();
 
         return controller;
@@ -302,10 +310,10 @@
     private void enableAccessibilityFloatingMenuConfig() {
         Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
-                UserHandle.USER_CURRENT);
+                ActivityManager.getCurrentUser());
         Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
-                UserHandle.USER_CURRENT);
+                ActivityManager.getCurrentUser());
     }
 
     private void captureKeyguardUpdateMonitorCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
index 558261b..04345fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java
@@ -31,6 +31,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 import org.junit.Before;
@@ -55,6 +56,8 @@
 
     @Mock
     private AccessibilityManager mAccessibilityManager;
+    @Mock
+    private SecureSettings mSecureSettings;
 
     private AccessibilityFloatingMenuView mMenuView;
     private AccessibilityFloatingMenu mMenu;
@@ -69,7 +72,7 @@
 
         final Position position = new Position(0, 0);
         mMenuView = new AccessibilityFloatingMenuView(mContext, position);
-        mMenu = new AccessibilityFloatingMenu(mContext, mMenuView);
+        mMenu = new AccessibilityFloatingMenu(mContext, mSecureSettings, mMenuView);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index a61cd23..578e1d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -15,6 +15,7 @@
 import android.view.RemoteAnimationTarget
 import android.view.SurfaceControl
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -26,6 +27,7 @@
 import junit.framework.AssertionFailedError
 import kotlin.concurrent.thread
 import org.junit.After
+import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -195,6 +197,13 @@
         verify(controller).onLaunchAnimationStart(anyBoolean())
     }
 
+    @Test
+    fun creatingControllerFromNormalViewThrows() {
+        assertThrows(IllegalArgumentException::class.java) {
+            ActivityLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+        }
+    }
+
     private fun fakeWindow(): RemoteAnimationTarget {
         val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
         val taskInfo = ActivityManager.RunningTaskInfo()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index cac4a0e..1e62fd23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -26,6 +26,7 @@
 import junit.framework.Assert.assertTrue
 import org.junit.After
 import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -260,6 +261,13 @@
         assertThat(touchSurface.visibility).isEqualTo(View.GONE)
     }
 
+    @Test
+    fun creatingControllerFromNormalViewThrows() {
+        assertThrows(IllegalArgumentException::class.java) {
+            DialogLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+        }
+    }
+
     private fun createAndShowDialog(
         animator: DialogLaunchAnimator = dialogLaunchAnimator,
     ): TestDialog {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index 3696ec5..0798d73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -16,58 +16,34 @@
 
 package com.android.systemui.animation
 
-import android.graphics.drawable.Drawable
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewParent
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.SysuiTestCase
-import org.junit.Before
+import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
-    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock lateinit var view: View
-    @Mock lateinit var rootView: ViewGroup
-    @Mock lateinit var viewParent: ViewParent
-    @Mock lateinit var drawable: Drawable
-    lateinit var controller: GhostedViewLaunchAnimatorController
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        whenever(view.rootView).thenReturn(rootView)
-        whenever(view.background).thenReturn(drawable)
-        whenever(view.height).thenReturn(0)
-        whenever(view.width).thenReturn(0)
-        whenever(view.parent).thenReturn(viewParent)
-        whenever(view.visibility).thenReturn(View.VISIBLE)
-        whenever(view.invalidate()).then { /* NO-OP */ }
-        whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ }
-        whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
-        whenever(interactionJankMonitor.end(anyInt())).thenReturn(true)
-        controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor)
-    }
-
     @Test
     fun animatingOrphanViewDoesNotCrash() {
         val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
 
+        val controller = GhostedViewLaunchAnimatorController(LaunchableFrameLayout(mContext))
         controller.onIntentStarted(willAnimate = true)
         controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
         controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
         controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
     }
+
+    @Test
+    fun creatingControllerFromNormalViewThrows() {
+        assertThrows(IllegalArgumentException::class.java) {
+            GhostedViewLaunchAnimatorController(FrameLayout(mContext))
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
new file mode 100644
index 0000000..3bdbf97
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackAnimationSpecTest.kt
@@ -0,0 +1,87 @@
+package com.android.systemui.animation.back
+
+import android.util.DisplayMetrics
+import android.window.BackEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private data class BackInput(val progressX: Float, val progressY: Float, val edge: Int)
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BackAnimationSpecTest : SysuiTestCase() {
+    private var displayMetrics =
+        DisplayMetrics().apply {
+            widthPixels = 100
+            heightPixels = 200
+            density = 3f
+        }
+
+    @Test
+    fun sysUi_floatingSystemSurfaces_animationValues() {
+        val maxX = 14.0f
+        val maxY = 4.0f
+        val minScale = 0.8f
+
+        val backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics)
+
+        assertBackTransformation(
+            backAnimationSpec = backAnimationSpec,
+            backInput = BackInput(progressX = 0f, progressY = 0f, edge = BackEvent.EDGE_LEFT),
+            expected = BackTransformation(translateX = 0f, translateY = 0f, scale = 1f),
+        )
+        assertBackTransformation(
+            backAnimationSpec = backAnimationSpec,
+            backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_LEFT),
+            expected = BackTransformation(translateX = -maxX, translateY = 0f, scale = minScale),
+        )
+        assertBackTransformation(
+            backAnimationSpec = backAnimationSpec,
+            backInput = BackInput(progressX = 1f, progressY = 0f, edge = BackEvent.EDGE_RIGHT),
+            expected = BackTransformation(translateX = maxX, translateY = 0f, scale = minScale),
+        )
+        assertBackTransformation(
+            backAnimationSpec = backAnimationSpec,
+            backInput = BackInput(progressX = 1f, progressY = 1f, edge = BackEvent.EDGE_LEFT),
+            expected = BackTransformation(translateX = -maxX, translateY = -maxY, scale = minScale),
+        )
+        assertBackTransformation(
+            backAnimationSpec = backAnimationSpec,
+            backInput = BackInput(progressX = 0f, progressY = 1f, edge = BackEvent.EDGE_LEFT),
+            expected = BackTransformation(translateX = 0f, translateY = -maxY, scale = 1f),
+        )
+        assertBackTransformation(
+            backAnimationSpec = backAnimationSpec,
+            backInput = BackInput(progressX = 0f, progressY = -1f, edge = BackEvent.EDGE_LEFT),
+            expected = BackTransformation(translateX = 0f, translateY = maxY, scale = 1f),
+        )
+    }
+}
+
+private fun assertBackTransformation(
+    backAnimationSpec: BackAnimationSpec,
+    backInput: BackInput,
+    expected: BackTransformation,
+) {
+    val actual = BackTransformation()
+    backAnimationSpec.getBackTransformation(
+        backEvent =
+            BackEvent(
+                /* touchX = */ 0f,
+                /* touchY = */ 0f,
+                /* progress = */ backInput.progressX,
+                /* swipeEdge = */ backInput.edge,
+            ),
+        progressY = backInput.progressY,
+        result = actual
+    )
+
+    val tolerance = 0f
+    assertThat(actual.translateX).isWithin(tolerance).of(expected.translateX)
+    assertThat(actual.translateY).isWithin(tolerance).of(expected.translateY)
+    assertThat(actual.scale).isWithin(tolerance).of(expected.scale)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
new file mode 100644
index 0000000..190b3d2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/BackTransformationTest.kt
@@ -0,0 +1,80 @@
+package com.android.systemui.animation.back
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BackTransformationTest : SysuiTestCase() {
+    private val targetView: View = mock()
+
+    @Test
+    fun defaultValue_noTransformation() {
+        val transformation = BackTransformation()
+
+        assertThat(transformation.translateX).isNaN()
+        assertThat(transformation.translateY).isNaN()
+        assertThat(transformation.scale).isNaN()
+    }
+
+    @Test
+    fun applyTo_targetView_translateX_Y_Scale() {
+        val transformation = BackTransformation(translateX = 0f, translateY = 0f, scale = 1f)
+
+        transformation.applyTo(targetView = targetView)
+
+        verify(targetView).translationX = 0f
+        verify(targetView).translationY = 0f
+        verify(targetView).scaleX = 1f
+        verify(targetView).scaleY = 1f
+        verifyNoMoreInteractions(targetView)
+    }
+
+    @Test
+    fun applyTo_targetView_translateX() {
+        val transformation = BackTransformation(translateX = 1f)
+
+        transformation.applyTo(targetView = targetView)
+
+        verify(targetView).translationX = 1f
+        verifyNoMoreInteractions(targetView)
+    }
+
+    @Test
+    fun applyTo_targetView_translateY() {
+        val transformation = BackTransformation(translateY = 2f)
+
+        transformation.applyTo(targetView = targetView)
+
+        verify(targetView).translationY = 2f
+        verifyNoMoreInteractions(targetView)
+    }
+
+    @Test
+    fun applyTo_targetView_scale() {
+        val transformation = BackTransformation(scale = 3f)
+
+        transformation.applyTo(targetView = targetView)
+
+        verify(targetView).scaleX = 3f
+        verify(targetView).scaleY = 3f
+        verifyNoMoreInteractions(targetView)
+    }
+
+    @Test
+    fun applyTo_targetView_noTransformation() {
+        val transformation = BackTransformation()
+
+        transformation.applyTo(targetView = targetView)
+
+        verifyNoMoreInteractions(targetView)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
new file mode 100644
index 0000000..921f9a8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
@@ -0,0 +1,63 @@
+package com.android.systemui.animation.back
+
+import android.util.DisplayMetrics
+import android.window.BackEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class OnBackAnimationCallbackExtensionTest : SysuiTestCase() {
+    private val onBackProgress: (BackTransformation) -> Unit = mock()
+    private val onBackStart: (BackEvent) -> Unit = mock()
+    private val onBackInvoke: () -> Unit = mock()
+    private val onBackCancel: () -> Unit = mock()
+
+    private val displayMetrics =
+        DisplayMetrics().apply {
+            widthPixels = 100
+            heightPixels = 100
+            density = 1f
+        }
+
+    private val onBackAnimationCallback =
+        onBackAnimationCallbackFrom(
+            backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(displayMetrics),
+            displayMetrics = displayMetrics,
+            onBackProgressed = onBackProgress,
+            onBackStarted = onBackStart,
+            onBackInvoked = onBackInvoke,
+            onBackCancelled = onBackCancel,
+        )
+
+    @Test
+    fun onBackProgressed_shouldInvoke_onBackProgress() {
+        val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+        onBackAnimationCallback.onBackStarted(backEvent)
+
+        onBackAnimationCallback.onBackProgressed(backEvent)
+
+        verify(onBackProgress).invoke(BackTransformation(0f, 0f, 1f))
+    }
+
+    @Test
+    fun onBackStarted_shouldInvoke_onBackStart() {
+        val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+
+        onBackAnimationCallback.onBackStarted(backEvent)
+
+        verify(onBackStart).invoke(backEvent)
+    }
+
+    @Test
+    fun onBackInvoked_shouldInvoke_onBackInvoke() {
+        onBackAnimationCallback.onBackInvoked()
+
+        verify(onBackInvoke).invoke()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 3c40835..41beada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -51,13 +51,22 @@
 import android.view.WindowMetrics
 import androidx.test.filters.SmallTest
 import com.airbnb.lottie.LottieAnimationView
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestCoroutineScope
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -100,14 +109,16 @@
     @Captor lateinit var overlayCaptor: ArgumentCaptor<View>
     @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
+    private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    private val featureFlags = FakeFeatureFlags()
     private val executor = FakeExecutor(FakeSystemClock())
     private lateinit var overlayController: ISidefpsController
     private lateinit var sideFpsController: SideFpsController
 
     enum class DeviceConfig {
         X_ALIGNED,
-        Y_ALIGNED_UNFOLDED,
-        Y_ALIGNED_FOLDED
+        Y_ALIGNED,
     }
 
     private lateinit var deviceConfig: DeviceConfig
@@ -121,6 +132,18 @@
 
     @Before
     fun setup() {
+        featureFlags.set(MODERN_ALTERNATE_BOUNCER, true)
+        keyguardBouncerRepository = FakeKeyguardBouncerRepository()
+        alternateBouncerInteractor =
+            AlternateBouncerInteractor(
+                keyguardBouncerRepository,
+                FakeBiometricSettingsRepository(),
+                FakeDeviceEntryFingerprintAuthRepository(),
+                FakeSystemClock(),
+                mock(KeyguardUpdateMonitor::class.java),
+                featureFlags,
+            )
+
         context.addMockSystemService(DisplayManager::class.java, displayManager)
         context.addMockSystemService(WindowManager::class.java, windowManager)
 
@@ -143,6 +166,7 @@
 
     private fun testWithDisplay(
         deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
+        isReverseDefaultRotation: Boolean = false,
         initInfo: DisplayInfo.() -> Unit = {},
         windowInsets: WindowInsets = insetsForSmallNavbar(),
         block: () -> Unit
@@ -151,27 +175,21 @@
 
         when (deviceConfig) {
             DeviceConfig.X_ALIGNED -> {
-                displayWidth = 2560
-                displayHeight = 1600
-                sensorLocation = SensorLocationInternal("", 2325, 0, 0)
-                boundsWidth = 160
-                boundsHeight = 84
+                displayWidth = 3000
+                displayHeight = 1500
+                sensorLocation = SensorLocationInternal("", 2500, 0, 0)
+                boundsWidth = 200
+                boundsHeight = 100
             }
-            DeviceConfig.Y_ALIGNED_UNFOLDED -> {
-                displayWidth = 2208
-                displayHeight = 1840
-                sensorLocation = SensorLocationInternal("", 0, 510, 0)
-                boundsWidth = 110
-                boundsHeight = 210
-            }
-            DeviceConfig.Y_ALIGNED_FOLDED -> {
-                displayWidth = 1080
-                displayHeight = 2100
-                sensorLocation = SensorLocationInternal("", 0, 590, 0)
-                boundsWidth = 110
-                boundsHeight = 210
+            DeviceConfig.Y_ALIGNED -> {
+                displayWidth = 2500
+                displayHeight = 2000
+                sensorLocation = SensorLocationInternal("", 0, 300, 0)
+                boundsWidth = 100
+                boundsHeight = 200
             }
         }
+
         indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
         displayBounds = Rect(0, 0, displayWidth, displayHeight)
         var locations = listOf(sensorLocation)
@@ -194,8 +212,10 @@
 
         val displayInfo = DisplayInfo()
         displayInfo.initInfo()
+
         val dmGlobal = mock(DisplayManagerGlobal::class.java)
         val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
+
         whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
         whenEver(windowManager.defaultDisplay).thenReturn(display)
         whenEver(windowManager.maximumWindowMetrics)
@@ -203,9 +223,15 @@
         whenEver(windowManager.currentWindowMetrics)
             .thenReturn(WindowMetrics(displayBounds, windowInsets))
 
+        val sideFpsControllerContext = context.createDisplayContext(display) as SysuiTestableContext
+        sideFpsControllerContext.orCreateTestableResources.addOverride(
+            com.android.internal.R.bool.config_reverseDefaultRotation,
+            isReverseDefaultRotation
+        )
+
         sideFpsController =
             SideFpsController(
-                context.createDisplayContext(display),
+                sideFpsControllerContext,
                 layoutInflater,
                 fingerprintManager,
                 windowManager,
@@ -214,7 +240,10 @@
                 displayManager,
                 executor,
                 handler,
-                dumpManager
+                alternateBouncerInteractor,
+                TestCoroutineScope(),
+                featureFlags,
+                dumpManager,
             )
 
         overlayController =
@@ -299,82 +328,235 @@
     }
 
     @Test
-    fun showsWithTaskbar() =
-        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_0 }) {
-            hidesWithTaskbar(visible = true)
-        }
-
-    @Test
-    fun showsWithTaskbarOnY() =
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_0() =
         testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
             { rotation = Surface.ROTATION_0 }
-        ) { hidesWithTaskbar(visible = true) }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
 
     @Test
-    fun showsWithTaskbar90() =
-        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_90 }) {
-            hidesWithTaskbar(visible = true)
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_90 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarCollapsedDownForXAlignedSensor_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForSmallNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun hidesSfpsIndicatorWhenOccludingTaskbarForXAlignedSensor_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForLargeNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_270 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_0 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_90 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarCollapsedDownForXAlignedSensor_InReverseDefaultRotation_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_90 },
+            windowInsets = insetsForSmallNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun hidesSfpsIndicatorWhenOccludingTaskbarForXAlignedSensor_InReverseDefaultRotation_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_90 },
+            windowInsets = insetsForLargeNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_180 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_90 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_180 },
+        ) {
+            verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true)
         }
 
     @Test
-    fun showsWithTaskbar90OnY() =
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_270() =
         testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-            { rotation = Surface.ROTATION_90 }
-        ) { hidesWithTaskbar(visible = true) }
-
-    @Test
-    fun showsWithTaskbar180() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.X_ALIGNED,
-            { rotation = Surface.ROTATION_180 }
-        ) { hidesWithTaskbar(visible = true) }
-
-    @Test
-    fun showsWithTaskbar270OnY() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
             { rotation = Surface.ROTATION_270 }
-        ) { hidesWithTaskbar(visible = true) }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
 
     @Test
-    fun showsWithTaskbarCollapsedDown() =
+    fun showsSfpsIndicatorWithTaskbarCollapsedDownForYAlignedSensor_270() =
         testWithDisplay(
-            deviceConfig = DeviceConfig.X_ALIGNED,
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
             { rotation = Surface.ROTATION_270 },
             windowInsets = insetsForSmallNavbar()
-        ) { hidesWithTaskbar(visible = true) }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
 
     @Test
-    fun showsWithTaskbarCollapsedDownOnY() =
+    fun hidesSfpsIndicatorWhenOccludingTaskbarForYAlignedSensor_270() =
         testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
-            { rotation = Surface.ROTATION_180 },
-            windowInsets = insetsForSmallNavbar()
-        ) { hidesWithTaskbar(visible = true) }
-
-    @Test
-    fun hidesWithTaskbarDown() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.X_ALIGNED,
-            { rotation = Surface.ROTATION_180 },
-            windowInsets = insetsForLargeNavbar()
-        ) { hidesWithTaskbar(visible = false) }
-
-    @Test
-    fun hidesWithTaskbarDownOnY() =
-        testWithDisplay(
-            deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
             { rotation = Surface.ROTATION_270 },
             windowInsets = insetsForLargeNavbar()
-        ) { hidesWithTaskbar(visible = true) }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_0 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_90() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_90 },
+        ) {
+            verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true)
+        }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_180 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarCollapsedDownForYAlignedSensor_InReverseDefaultRotation_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForSmallNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    @Test
+    fun hidesSfpsIndicatorWhenOccludingTaskbarForYAlignedSensor_InReverseDefaultRotation_180() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_180 },
+            windowInsets = insetsForLargeNavbar()
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) }
+
+    @Test
+    fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) { verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) }
+
+    private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) {
+        sideFpsController.overlayOffsets = sensorLocation
+    }
+
+    fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay {
+        // WHEN alternate bouncer is visible
+        keyguardBouncerRepository.setAlternateVisible(true)
+        executor.runAllReady()
+
+        // THEN side fps shows UI
+        verify(windowManager).addView(any(), any())
+        verify(windowManager, never()).removeView(any())
+
+        // WHEN alternate bouncer is no longer visible
+        keyguardBouncerRepository.setAlternateVisible(false)
+        executor.runAllReady()
+
+        // THEN side fps UI is hidden
+        verify(windowManager).removeView(any())
+    }
 
     private fun hidesWithTaskbar(visible: Boolean) {
         overlayController.show(SENSOR_ID, REASON_UNKNOWN)
         executor.runAllReady()
 
-        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(visible, false)
+        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(true, false)
         executor.runAllReady()
 
         verify(windowManager).addView(any(), any())
@@ -382,25 +564,100 @@
         verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE
     }
 
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
+     * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
+     * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
+     * in other rotations have been omitted.
+     */
     @Test
-    fun testIndicatorPlacementForXAlignedSensor() =
-        testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED) {
-            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+    fun verifiesIndicatorPlacementForXAlignedSensor_0() {
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 }
+        ) {
             sideFpsController.overlayOffsets = sensorLocation
+
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
             executor.runAllReady()
 
             verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
             assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
             assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
         }
+    }
 
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
+     * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
+     * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
+     * correctly, tests for indicator placement in other rotations have been omitted.
+     */
     @Test
-    fun testIndicatorPlacementForYAlignedSensor() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+    fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() {
+        testWithDisplay(
+            deviceConfig = DeviceConfig.X_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) {
             sideFpsController.overlayOffsets = sensorLocation
+
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
+        }
+    }
+
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
+     * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
+     * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
+     * in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForYAlignedSensor_0() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = false,
+            { rotation = Surface.ROTATION_0 }
+        ) {
+            sideFpsController.overlayOffsets = sensorLocation
+
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
+            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+            executor.runAllReady()
+
+            verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+            assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
+        }
+
+    /**
+     * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
+     * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
+     * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
+     * correctly, tests for indicator placement in other rotations have been omitted.
+     */
+    @Test
+    fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
+        testWithDisplay(
+            deviceConfig = DeviceConfig.Y_ALIGNED,
+            isReverseDefaultRotation = true,
+            { rotation = Surface.ROTATION_270 }
+        ) {
+            sideFpsController.overlayOffsets = sensorLocation
+
+            sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+
             overlayController.show(SENSOR_ID, REASON_UNKNOWN)
             executor.runAllReady()
 
@@ -412,7 +669,6 @@
     @Test
     fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
         // By default all those tests assume the side fps sensor is available.
-
         assertThat(fingerprintManager.hasSideFpsSensor()).isTrue()
     }
 
@@ -425,7 +681,7 @@
 
     @Test
     fun testLayoutParams_isKeyguardDialogType() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
             sideFpsController.overlayOffsets = sensorLocation
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
             overlayController.show(SENSOR_ID, REASON_UNKNOWN)
@@ -440,7 +696,7 @@
 
     @Test
     fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
             sideFpsController.overlayOffsets = sensorLocation
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
             overlayController.show(SENSOR_ID, REASON_UNKNOWN)
@@ -455,7 +711,7 @@
 
     @Test
     fun testLayoutParams_hasTrustedOverlayWindowFlag() =
-        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+        testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
             sideFpsController.overlayOffsets = sensorLocation
             sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
             overlayController.show(SENSOR_ID, REASON_UNKNOWN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 1ef119d..0690d1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -53,6 +53,7 @@
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -99,6 +100,7 @@
     @Mock private lateinit var unlockedScreenOffAnimationController:
             UnlockedScreenOffAnimationController
     @Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
+    @Mock private lateinit var secureSettings: SecureSettings
     @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
     @Mock private lateinit var udfpsController: UdfpsController
     @Mock private lateinit var udfpsView: UdfpsView
@@ -139,8 +141,8 @@
             statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
             keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
             configurationController, keyguardStateController,
-            unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
-            controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
+            unlockedScreenOffAnimationController, udfpsDisplayMode, secureSettings, REQUEST_ID,
+            reason, controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
             primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable,
         )
         block()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 0c34e54..232daad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -96,6 +96,7 @@
 import com.android.systemui.util.concurrency.Execution;
 import com.android.systemui.util.concurrency.FakeExecution;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.time.SystemClock;
 
@@ -205,6 +206,8 @@
     private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
     @Mock
     private AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @Mock
+    private SecureSettings mSecureSettings;
 
     // Capture listeners so that they can be used to send events
     @Captor
@@ -296,7 +299,7 @@
                 mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
                 mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
                 mPrimaryBouncerInteractor, mSinglePointerTouchProcessor,
-                mAlternateBouncerInteractor);
+                mAlternateBouncerInteractor, mSecureSettings);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index 9c32c38..dbbc266 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -37,7 +37,6 @@
 import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -71,7 +70,6 @@
     protected @Mock SystemUIDialogManager mDialogManager;
     protected @Mock UdfpsController mUdfpsController;
     protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
-    protected @Mock KeyguardBouncer mBouncer;
     protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
 
@@ -149,11 +147,8 @@
 
     protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
             boolean useModernBouncer, boolean useExpandedOverlay) {
-        mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
         mFeatureFlags.set(Flags.MODERN_ALTERNATE_BOUNCER, useModernBouncer);
         mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
-        when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(
-                useModernBouncer ? null : mBouncer);
         UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
                 mView,
                 mStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 813eeeb..f437a8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -34,22 +34,15 @@
 
 import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
-    private @Captor ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback>
-            mBouncerExpansionCallbackCaptor;
-    private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
-
     @Override
     public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
         return createUdfpsKeyguardViewController(/* useModernBouncer */ false,
@@ -62,11 +55,9 @@
         captureStatusBarStateListeners();
         sendStatusBarStateChanged(StatusBarState.KEYGUARD);
 
-        captureBouncerExpansionCallback();
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
-        mBouncerExpansionCallback.onVisibilityChanged(true);
-
+        when(mView.getUnpausedAlpha()).thenReturn(0);
         assertTrue(mController.shouldPauseAuth());
     }
 
@@ -304,11 +295,6 @@
         verify(mView, atLeastOnce()).setPauseAuth(false);
     }
 
-    private void captureBouncerExpansionCallback() {
-        verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
-        mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
-    }
-
     @Test
     // TODO(b/259264861): Tracking Bug
     public void testUdfpsExpandedOverlayOn() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 9060922..c73ff1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -26,8 +26,10 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
-import com.android.systemui.keyguard.data.repository.BiometricRepository
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
@@ -64,7 +66,7 @@
         allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
         MockitoAnnotations.initMocks(this)
         keyguardBouncerRepository =
-            KeyguardBouncerRepository(
+            KeyguardBouncerRepositoryImpl(
                 mock(com.android.keyguard.ViewMediatorCallback::class.java),
                 FakeSystemClock(),
                 TestCoroutineScope(),
@@ -90,7 +92,8 @@
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
                 keyguardBouncerRepository,
-                mock(BiometricRepository::class.java),
+                mock(BiometricSettingsRepository::class.java),
+                mock(DeviceEntryFingerprintAuthRepository::class.java),
                 mock(SystemClock::class.java),
                 mock(KeyguardUpdateMonitor::class.java),
                 mock(FeatureFlags::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index 34ddf79..8e20303 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -110,6 +110,28 @@
                         expectedInteractionEvent = InteractionEvent.UP,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
+                    // MotionEvent.ACTION_HOVER_ENTER
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_ENTER,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
                     // MotionEvent.ACTION_MOVE
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_MOVE,
@@ -161,6 +183,35 @@
                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
                         expectedPointerOnSensorId = POINTER_ID_2,
                     ),
+                    // MotionEvent.ACTION_HOVER_MOVE
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.DOWN,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = POINTER_ID_1,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_MOVE,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
                     // MotionEvent.ACTION_UP
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_UP,
@@ -183,6 +234,28 @@
                         expectedInteractionEvent = InteractionEvent.UNCHANGED,
                         expectedPointerOnSensorId = INVALID_POINTER_ID,
                     ),
+                    // MotionEvent.ACTION_HOVER_EXIT
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
+                        previousPointerOnSensorId = POINTER_ID_1,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = true)),
+                        expectedInteractionEvent = InteractionEvent.UP,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
+                    genPositiveTestCases(
+                        motionEventAction = MotionEvent.ACTION_HOVER_EXIT,
+                        previousPointerOnSensorId = INVALID_POINTER_ID,
+                        currentPointers = listOf(TestPointer(id = POINTER_ID_1, onSensor = false)),
+                        expectedInteractionEvent = InteractionEvent.UNCHANGED,
+                        expectedPointerOnSensorId = INVALID_POINTER_ID,
+                    ),
                     // MotionEvent.ACTION_CANCEL
                     genPositiveTestCases(
                         motionEventAction = MotionEvent.ACTION_CANCEL,
@@ -315,13 +388,7 @@
                         expectedPointerOnSensorId = POINTER_ID_2
                     )
                 )
-                .flatten() +
-                listOf(
-                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_ENTER),
-                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_MOVE),
-                        genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT)
-                    )
-                    .flatten()
+                .flatten()
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index 262b4b8..80c3e5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -69,6 +70,8 @@
     lateinit var cameraIntents: CameraIntentsWrapper
     @Mock
     lateinit var contentResolver: ContentResolver
+    @Mock
+    lateinit var userTracker: UserTracker
 
     private lateinit var underTest: CameraGestureHelper
 
@@ -96,6 +99,7 @@
             cameraIntents = cameraIntents,
             contentResolver = contentResolver,
             uiExecutor = MoreExecutors.directExecutor(),
+            userTracker = userTracker,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index d159714..d6cafcb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -16,18 +16,23 @@
 
 package com.android.systemui.charging
 
+import android.graphics.Rect
 import android.testing.AndroidTestingRunner
+import android.view.Surface
 import android.view.View
 import android.view.WindowManager
+import android.view.WindowMetrics
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.surfaceeffects.ripple.RippleView
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.surfaceeffects.ripple.RippleView
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.Before
 import org.junit.Test
@@ -35,12 +40,12 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers
 import org.mockito.Mock
+import org.mockito.Mockito.`when`
 import org.mockito.Mockito.any
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -54,6 +59,7 @@
     @Mock private lateinit var rippleView: RippleView
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var windowMetrics: WindowMetrics
     private val systemClock = FakeSystemClock()
 
     @Before
@@ -66,6 +72,9 @@
         rippleView.setupShader()
         controller.rippleView = rippleView // Replace the real ripple view with a mock instance
         controller.registerCallbacks()
+
+        `when`(windowMetrics.bounds).thenReturn(Rect(0, 0, 100, 100))
+        `when`(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
     }
 
     @Test
@@ -164,4 +173,63 @@
         verify(rippleView, never()).addOnAttachStateChangeListener(attachListenerCaptor.capture())
         verify(windowManager, never()).addView(eq(rippleView), any<WindowManager.LayoutParams>())
     }
+
+    @Test
+    fun testRipple_layoutsCorrectly() {
+        // Sets the correct ripple size.
+        val width = 100
+        val height = 200
+        whenever(windowMetrics.bounds).thenReturn(Rect(0, 0, width, height))
+
+        // Trigger ripple.
+        val captor = ArgumentCaptor
+                .forClass(BatteryController.BatteryStateChangeCallback::class.java)
+        verify(batteryController).addCallback(captor.capture())
+
+        captor.value.onBatteryLevelChanged(
+                /* unusedBatteryLevel= */ 0,
+                /* plugged in= */ true,
+                /* charging= */ false)
+
+        val attachListenerCaptor =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+        verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
+        verify(windowManager).addView(eq(rippleView), any<WindowManager.LayoutParams>())
+
+        val runnableCaptor =
+                ArgumentCaptor.forClass(Runnable::class.java)
+        attachListenerCaptor.value.onViewAttachedToWindow(rippleView)
+        verify(rippleView).startRipple(runnableCaptor.capture())
+
+        // Verify size and center position.
+        val maxSize = 400f // Double the max value between width and height.
+        verify(rippleView).setMaxSize(maxWidth = maxSize, maxHeight = maxSize)
+
+        val normalizedPortPosX =
+                context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_x)
+        val normalizedPortPosY =
+                context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_y)
+        val expectedCenterX: Float
+        val expectedCenterY: Float
+        when (context.display.rotation) {
+            Surface.ROTATION_90 -> {
+                expectedCenterX = width * normalizedPortPosY
+                expectedCenterY = height * (1 - normalizedPortPosX)
+            }
+            Surface.ROTATION_180 -> {
+                expectedCenterX = width * (1 - normalizedPortPosX)
+                expectedCenterY = height * (1 - normalizedPortPosY)
+            }
+            Surface.ROTATION_270 -> {
+                expectedCenterX = width * (1 - normalizedPortPosY)
+                expectedCenterY = height * normalizedPortPosX
+            }
+            else -> { // Surface.ROTATION_0
+                expectedCenterX = width * normalizedPortPosX
+                expectedCenterY = height * normalizedPortPosY
+            }
+        }
+
+        verify(rippleView).setCenter(expectedCenterX, expectedCenterY)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index bdd496e..7177919 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.clipboardoverlay;
 
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
 
 import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
 
@@ -33,7 +33,6 @@
 import android.content.ClipDescription;
 import android.content.ClipboardManager;
 import android.os.PersistableBundle;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 
 import androidx.test.filters.SmallTest;
@@ -41,9 +40,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.flags.FakeFeatureFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -63,18 +60,12 @@
     @Mock
     private ClipboardManager mClipboardManager;
     @Mock
-    private ClipboardOverlayControllerLegacyFactory mClipboardOverlayControllerLegacyFactory;
-    @Mock
-    private ClipboardOverlayControllerLegacy mOverlayControllerLegacy;
-    @Mock
     private ClipboardOverlayController mOverlayController;
     @Mock
     private ClipboardToast mClipboardToast;
+    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock
     private UiEventLogger mUiEventLogger;
-    @Mock
-    private FeatureFlags mFeatureFlags;
-    private DeviceConfigProxyFake mDeviceConfigProxy;
 
     private ClipData mSampleClipData;
     private String mSampleSource = "Example source";
@@ -97,8 +88,6 @@
         mOverlayControllerProvider = () -> mOverlayController;
 
         MockitoAnnotations.initMocks(this);
-        when(mClipboardOverlayControllerLegacyFactory.create(any()))
-                .thenReturn(mOverlayControllerLegacy);
         when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
         Settings.Secure.putInt(
                 mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 1);
@@ -108,26 +97,15 @@
         when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
         when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
 
-        mDeviceConfigProxy = new DeviceConfigProxyFake();
+        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true);
 
-        mClipboardListener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardToast, mClipboardManager, mUiEventLogger, mFeatureFlags);
+        mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
+                mClipboardToast, mClipboardManager, mFeatureFlags, mUiEventLogger);
     }
 
-    @Test
-    public void test_disabled() {
-        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
-                "false", false);
-        mClipboardListener.start();
-        verifyZeroInteractions(mClipboardManager);
-        verifyZeroInteractions(mUiEventLogger);
-    }
 
     @Test
-    public void test_enabled() {
-        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
-                "true", false);
+    public void test_initialization() {
         mClipboardListener.start();
         verify(mClipboardManager).addPrimaryClipChangedListener(any());
         verifyZeroInteractions(mUiEventLogger);
@@ -135,45 +113,6 @@
 
     @Test
     public void test_consecutiveCopies() {
-        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
-
-        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
-                "true", false);
-        mClipboardListener.start();
-        mClipboardListener.onPrimaryClipChanged();
-
-        verify(mClipboardOverlayControllerLegacyFactory).create(any());
-
-        verify(mOverlayControllerLegacy).setClipData(
-                mClipDataCaptor.capture(), mStringCaptor.capture());
-
-        assertEquals(mSampleClipData, mClipDataCaptor.getValue());
-        assertEquals(mSampleSource, mStringCaptor.getValue());
-
-        verify(mOverlayControllerLegacy).setOnSessionCompleteListener(mRunnableCaptor.capture());
-
-        // Should clear the overlay controller
-        mRunnableCaptor.getValue().run();
-
-        mClipboardListener.onPrimaryClipChanged();
-
-        verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
-
-        // Not calling the runnable here, just change the clip again and verify that the overlay is
-        // NOT recreated.
-
-        mClipboardListener.onPrimaryClipChanged();
-
-        verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
-        verifyZeroInteractions(mOverlayControllerProvider);
-    }
-
-    @Test
-    public void test_consecutiveCopies_new() {
-        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
-
-        mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
-                "true", false);
         mClipboardListener.start();
         mClipboardListener.onPrimaryClipChanged();
 
@@ -200,7 +139,6 @@
         mClipboardListener.onPrimaryClipChanged();
 
         verify(mOverlayControllerProvider, times(2)).get();
-        verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
     }
 
     @Test
@@ -231,23 +169,6 @@
 
     @Test
     public void test_logging_enterAndReenter() {
-        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
-
-        mClipboardListener.start();
-
-        mClipboardListener.onPrimaryClipChanged();
-        mClipboardListener.onPrimaryClipChanged();
-
-        verify(mUiEventLogger, times(1)).log(
-                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
-        verify(mUiEventLogger, times(1)).log(
-                ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
-    }
-
-    @Test
-    public void test_logging_enterAndReenter_new() {
-        when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
-
         mClipboardListener.start();
 
         mClipboardListener.onPrimaryClipChanged();
@@ -271,6 +192,35 @@
                 ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
         verify(mClipboardToast, times(1)).showCopiedToast();
         verifyZeroInteractions(mOverlayControllerProvider);
-        verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
+    }
+
+    @Test
+    public void test_minimizedLayoutFlagOff_usesLegacy() {
+        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mOverlayControllerProvider).get();
+
+        verify(mOverlayController).setClipDataLegacy(
+                mClipDataCaptor.capture(), mStringCaptor.capture());
+
+        assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+        assertEquals(mSampleSource, mStringCaptor.getValue());
+    }
+
+    @Test
+    public void test_minimizedLayoutFlagOn_usesNew() {
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mOverlayControllerProvider).get();
+
+        verify(mOverlayController).setClipData(
+                mClipDataCaptor.capture(), mStringCaptor.capture());
+
+        assertEquals(mSampleClipData, mClipDataCaptor.getValue());
+        assertEquals(mSampleSource, mStringCaptor.getValue());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
new file mode 100644
index 0000000..faef35e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 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.systemui.clipboardoverlay
+
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ContentResolver
+import android.content.Context
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.PersistableBundle
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.whenever
+import java.io.IOException
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ClipboardModelTest : SysuiTestCase() {
+    @Mock private lateinit var mClipboardUtils: ClipboardOverlayUtils
+    @Mock private lateinit var mMockContext: Context
+    @Mock private lateinit var mMockContentResolver: ContentResolver
+    private lateinit var mSampleClipData: ClipData
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mSampleClipData = ClipData("Test", arrayOf("text/plain"), ClipData.Item("Test Item"))
+    }
+
+    @Test
+    fun test_nullClipData() {
+        val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, null, "test source")
+        assertNull(model.clipData)
+        assertEquals("test source", model.source)
+        assertEquals(ClipboardModel.Type.OTHER, model.type)
+        assertNull(model.item)
+        assertFalse(model.isSensitive)
+        assertFalse(model.isRemote)
+        assertNull(model.loadThumbnail(mContext))
+    }
+
+    @Test
+    fun test_textClipData() {
+        val source = "test source"
+        val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, mSampleClipData, source)
+        assertEquals(mSampleClipData, model.clipData)
+        assertEquals(source, model.source)
+        assertEquals(ClipboardModel.Type.TEXT, model.type)
+        assertEquals(mSampleClipData.getItemAt(0), model.item)
+        assertFalse(model.isSensitive)
+        assertFalse(model.isRemote)
+        assertNull(model.loadThumbnail(mContext))
+    }
+
+    @Test
+    fun test_sensitiveExtra() {
+        val description = mSampleClipData.description
+        val b = PersistableBundle()
+        b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
+        description.extras = b
+        val data = ClipData(description, mSampleClipData.getItemAt(0))
+        val (_, _, _, _, sensitive) =
+            ClipboardModel.fromClipData(mContext, mClipboardUtils, data, "")
+        assertTrue(sensitive)
+    }
+
+    @Test
+    fun test_remoteExtra() {
+        whenever(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true)
+        val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, mSampleClipData, "")
+        assertTrue(model.isRemote)
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun test_imageClipData() {
+        val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
+        whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+        whenever(mMockContext.resources).thenReturn(mContext.resources)
+        whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenReturn(testBitmap)
+        whenever(mMockContentResolver.getType(any())).thenReturn("image")
+        val imageClipData =
+            ClipData("Test", arrayOf("text/plain"), ClipData.Item(Uri.parse("test")))
+        val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+        assertEquals(ClipboardModel.Type.IMAGE, model.type)
+        assertEquals(testBitmap, model.loadThumbnail(mMockContext))
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun test_imageClipData_loadFailure() {
+        whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+        whenever(mMockContext.resources).thenReturn(mContext.resources)
+        whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenThrow(IOException())
+        whenever(mMockContentResolver.getType(any())).thenReturn("image")
+        val imageClipData =
+            ClipData("Test", arrayOf("text/plain"), ClipData.Item(Uri.parse("test")))
+        val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+        assertEquals(ClipboardModel.Type.IMAGE, model.type)
+        assertNull(model.loadThumbnail(mMockContext))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index b4e85c0..0ac2667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -16,13 +16,17 @@
 
 package com.android.systemui.clipboardoverlay;
 
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
 import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -35,8 +39,11 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.PersistableBundle;
+import android.view.WindowInsets;
 import android.view.textclassifier.TextLinks;
 
 import androidx.test.filters.SmallTest;
@@ -47,6 +54,7 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.screenshot.TimeoutHandler;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -81,6 +89,7 @@
     private ClipboardOverlayUtils mClipboardUtils;
     @Mock
     private UiEventLogger mUiEventLogger;
+    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     @Mock
@@ -100,11 +109,14 @@
 
         when(mClipboardOverlayView.getEnterAnimation()).thenReturn(mAnimator);
         when(mClipboardOverlayView.getExitAnimation()).thenReturn(mAnimator);
+        when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
+                getImeInsets(new Rect(0, 0, 0, 0)));
 
         mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
                 new ClipData.Item("Test Item"));
 
         mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
+        mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true); // turned off for legacy tests
 
         mOverlayController = new ClipboardOverlayController(
                 mContext,
@@ -127,6 +139,159 @@
     }
 
     @Test
+    public void test_setClipData_nullData_legacy() {
+        ClipData clipData = null;
+        mOverlayController.setClipDataLegacy(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(0)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_invalidImageData_legacy() {
+        ClipData clipData = new ClipData("", new String[]{"image/png"},
+                new ClipData.Item(Uri.parse("")));
+
+        mOverlayController.setClipDataLegacy(clipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+        verify(mClipboardOverlayView, times(0)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_textData_legacy() {
+        mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_sensitiveTextData_legacy() {
+        ClipDescription description = mSampleClipData.getDescription();
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
+        description.setExtras(b);
+        ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
+        mOverlayController.setClipDataLegacy(data, "");
+
+        verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
+        verify(mClipboardOverlayView, times(1)).showShareChip();
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_setClipData_repeatedCalls_legacy() {
+        when(mAnimator.isRunning()).thenReturn(true);
+
+        mOverlayController.setClipDataLegacy(mSampleClipData, "");
+        mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+        verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+    }
+
+    @Test
+    public void test_viewCallbacks_onShareTapped_legacy() {
+        mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+        mCallbacks.onShareButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
+        verify(mClipboardOverlayView, times(1)).getExitAnimation();
+    }
+
+    @Test
+    public void test_viewCallbacks_onDismissTapped_legacy() {
+        mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
+        verify(mClipboardOverlayView, times(1)).getExitAnimation();
+    }
+
+    @Test
+    public void test_multipleDismissals_dismissesOnce_legacy() {
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+        mCallbacks.onSwipeDismissInitiated(mAnimator);
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
+        verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+    }
+
+    @Test
+    public void test_remoteCopy_withFlagOn_legacy() {
+        mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+        when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+        mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+        verify(mTimeoutHandler, never()).resetTimeout();
+    }
+
+    @Test
+    public void test_remoteCopy_withFlagOff_legacy() {
+        when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+        mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+        verify(mTimeoutHandler).resetTimeout();
+    }
+
+    @Test
+    public void test_nonRemoteCopy_legacy() {
+        mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+        when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
+
+        mOverlayController.setClipDataLegacy(mSampleClipData, "");
+
+        verify(mTimeoutHandler).resetTimeout();
+    }
+
+    @Test
+    public void test_logsUseLastClipSource_legacy() {
+        mOverlayController.setClipDataLegacy(mSampleClipData, "first.package");
+        mCallbacks.onDismissButtonTapped();
+        mOverlayController.setClipDataLegacy(mSampleClipData, "second.package");
+        mCallbacks.onDismissButtonTapped();
+
+        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
+        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+        verifyNoMoreInteractions(mUiEventLogger);
+    }
+
+    @Test
+    public void test_logOnClipboardActionsShown_legacy() {
+        ClipData.Item item = mSampleClipData.getItemAt(0);
+        item.setTextLinks(Mockito.mock(TextLinks.class));
+        mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+        when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
+                .thenReturn(true);
+        when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString()))
+                .thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
+        when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                ((Runnable) invocation.getArgument(0)).run();
+                return null;
+            }
+        });
+
+        mOverlayController.setClipDataLegacy(
+                new ClipData(mSampleClipData.getDescription(), item), "actionShownSource");
+        mExecutor.runAllReady();
+
+        verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
+        verifyNoMoreInteractions(mUiEventLogger);
+    }
+
+    // start of refactored setClipData tests
+    @Test
     public void test_setClipData_nullData() {
         ClipData clipData = null;
         mOverlayController.setClipData(clipData, "");
@@ -277,4 +442,43 @@
         verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
         verifyNoMoreInteractions(mUiEventLogger);
     }
+
+    @Test
+    public void test_noInsets_showsExpanded() {
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mClipboardOverlayView, never()).setMinimized(true);
+        verify(mClipboardOverlayView).setMinimized(false);
+        verify(mClipboardOverlayView).showTextPreview("Test Item", false);
+    }
+
+    @Test
+    public void test_insets_showsMinimized() {
+        when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
+                getImeInsets(new Rect(0, 0, 0, 1)));
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mClipboardOverlayView).setMinimized(true);
+        verify(mClipboardOverlayView, never()).setMinimized(false);
+        verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean());
+
+        mCallbacks.onMinimizedViewTapped();
+
+        verify(mClipboardOverlayView).setMinimized(false);
+        verify(mClipboardOverlayView).showTextPreview("Test Item", false);
+    }
+
+    @Test
+    public void test_insetsChanged_minimizes() {
+        mOverlayController.setClipData(mSampleClipData, "");
+        verify(mClipboardOverlayView, never()).setMinimized(true);
+
+        WindowInsets insetsWithKeyboard = getImeInsets(new Rect(0, 0, 0, 1));
+        mOverlayController.onInsetsChanged(insetsWithKeyboard, ORIENTATION_PORTRAIT);
+        verify(mClipboardOverlayView).setMinimized(true);
+    }
+
+    private static WindowInsets getImeInsets(Rect r) {
+        return new WindowInsets.Builder().setInsets(WindowInsets.Type.ime(), Insets.of(r)).build();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
new file mode 100644
index 0000000..fe352fd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.systemui.common.ui.view
+
+import android.view.ViewConfiguration
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel
+import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel.Down
+import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel.Move
+import com.android.systemui.common.ui.view.LongPressHandlingViewInteractionHandler.MotionEventModel.Up
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class LongPressHandlingViewInteractionHandlerTest : SysuiTestCase() {
+
+    @Mock private lateinit var postDelayed: (Runnable, Long) -> DisposableHandle
+    @Mock private lateinit var onLongPressDetected: (Int, Int) -> Unit
+    @Mock private lateinit var onSingleTapDetected: () -> Unit
+
+    private lateinit var underTest: LongPressHandlingViewInteractionHandler
+
+    private var isAttachedToWindow: Boolean = true
+    private var delayedRunnable: Runnable? = null
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(postDelayed.invoke(any(), any())).thenAnswer { invocation ->
+            delayedRunnable = invocation.arguments[0] as Runnable
+            DisposableHandle { delayedRunnable = null }
+        }
+
+        underTest =
+            LongPressHandlingViewInteractionHandler(
+                postDelayed = postDelayed,
+                isAttachedToWindow = { isAttachedToWindow },
+                onLongPressDetected = onLongPressDetected,
+                onSingleTapDetected = onSingleTapDetected,
+            )
+        underTest.isLongPressHandlingEnabled = true
+    }
+
+    @Test
+    fun `long-press`() = runTest {
+        val downX = 123
+        val downY = 456
+        dispatchTouchEvents(
+            Down(
+                x = downX,
+                y = downY,
+            ),
+            Move(
+                distanceMoved = ViewConfiguration.getTouchSlop() - 0.1f,
+            ),
+        )
+        delayedRunnable?.run()
+
+        verify(onLongPressDetected).invoke(downX, downY)
+        verify(onSingleTapDetected, never()).invoke()
+    }
+
+    @Test
+    fun `long-press but feature not enabled`() = runTest {
+        underTest.isLongPressHandlingEnabled = false
+        dispatchTouchEvents(
+            Down(
+                x = 123,
+                y = 456,
+            ),
+        )
+
+        assertThat(delayedRunnable).isNull()
+        verify(onLongPressDetected, never()).invoke(any(), any())
+        verify(onSingleTapDetected, never()).invoke()
+    }
+
+    @Test
+    fun `long-press but view not attached`() = runTest {
+        isAttachedToWindow = false
+        dispatchTouchEvents(
+            Down(
+                x = 123,
+                y = 456,
+            ),
+        )
+        delayedRunnable?.run()
+
+        verify(onLongPressDetected, never()).invoke(any(), any())
+        verify(onSingleTapDetected, never()).invoke()
+    }
+
+    @Test
+    fun `dragged too far to be considered a long-press`() = runTest {
+        dispatchTouchEvents(
+            Down(
+                x = 123,
+                y = 456,
+            ),
+            Move(
+                distanceMoved = ViewConfiguration.getTouchSlop() + 0.1f,
+            ),
+        )
+
+        assertThat(delayedRunnable).isNull()
+        verify(onLongPressDetected, never()).invoke(any(), any())
+        verify(onSingleTapDetected, never()).invoke()
+    }
+
+    @Test
+    fun `held down too briefly to be considered a long-press`() = runTest {
+        dispatchTouchEvents(
+            Down(
+                x = 123,
+                y = 456,
+            ),
+            Up(
+                distanceMoved = ViewConfiguration.getTouchSlop().toFloat(),
+                gestureDuration = ViewConfiguration.getLongPressTimeout() - 1L,
+            ),
+        )
+
+        assertThat(delayedRunnable).isNull()
+        verify(onLongPressDetected, never()).invoke(any(), any())
+        verify(onSingleTapDetected).invoke()
+    }
+
+    private fun dispatchTouchEvents(
+        vararg models: MotionEventModel,
+    ) {
+        models.forEach { model -> underTest.onTouchEvent(model) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 25f471b..e35b2a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.controls.ControlStatus
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.settings.UserFileManager
@@ -66,6 +67,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.clearInvocations
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -88,6 +90,8 @@
     private lateinit var userTracker: UserTracker
     @Mock
     private lateinit var userFileManager: UserFileManager
+    @Mock
+    private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
 
     @Captor
     private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
@@ -100,8 +104,6 @@
             ArgumentCaptor<ControlsBindingController.LoadCallback>
 
     @Captor
-    private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
-    @Captor
     private lateinit var listingCallbackCaptor:
             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
 
@@ -168,15 +170,12 @@
                 listingController,
                 userFileManager,
                 userTracker,
+                authorizedPanelsRepository,
                 Optional.of(persistenceWrapper),
                 mock(DumpManager::class.java)
         )
         controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
 
-        verify(userTracker).addCallback(
-            capture(userTrackerCallbackCaptor), any()
-        )
-
         verify(listingController).addCallback(capture(listingCallbackCaptor))
     }
 
@@ -224,6 +223,7 @@
                 listingController,
                 userFileManager,
                 userTracker,
+                authorizedPanelsRepository,
                 Optional.of(persistenceWrapper),
                 mock(DumpManager::class.java)
         )
@@ -231,6 +231,26 @@
     }
 
     @Test
+    fun testAddAuthorizedPackagesFromSavedFavoritesOnStart() {
+        clearInvocations(authorizedPanelsRepository)
+        `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_STRUCTURE_INFO))
+        ControlsControllerImpl(
+                mContext,
+                delayableExecutor,
+                uiController,
+                bindingController,
+                listingController,
+                userFileManager,
+                userTracker,
+                authorizedPanelsRepository,
+                Optional.of(persistenceWrapper),
+                mock(DumpManager::class.java)
+        )
+        verify(authorizedPanelsRepository)
+                .addAuthorizedPanels(setOf(TEST_STRUCTURE_INFO.componentName.packageName))
+    }
+
+    @Test
     fun testOnActionResponse() {
         controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
 
@@ -513,7 +533,7 @@
 
         reset(persistenceWrapper)
 
-        userTrackerCallbackCaptor.value.onUserChanged(otherUser, mContext)
+        controller.changeUser(UserHandle.of(otherUser))
 
         verify(persistenceWrapper).changeFileAndBackupManager(any(), any())
         verify(persistenceWrapper).readFavorites()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
index 765c4c0..226ef3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
@@ -21,12 +21,15 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.LayoutInflater
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.settingslib.core.lifecycle.Lifecycle
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -36,8 +39,10 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
+import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.text.Collator
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -49,25 +54,18 @@
     @Mock lateinit var lifecycle: Lifecycle
     @Mock lateinit var controlsListingController: ControlsListingController
     @Mock lateinit var layoutInflater: LayoutInflater
-    @Mock lateinit var onAppSelected: (ComponentName?) -> Unit
+    @Mock lateinit var onAppSelected: (ControlsServiceInfo) -> Unit
     @Mock lateinit var favoritesRenderer: FavoritesRenderer
     val resources: Resources = context.resources
     lateinit var adapter: AppAdapter
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        adapter = AppAdapter(backgroundExecutor,
-            uiExecutor,
-            lifecycle,
-            controlsListingController,
-            layoutInflater,
-            onAppSelected,
-            favoritesRenderer,
-            resources)
     }
 
     @Test
     fun testOnServicesUpdated_nullLoadLabel() {
+        adapter = createAdapterWithAuthorizedPanels(emptySet())
         val captor = ArgumentCaptor
             .forClass(ControlsListingController.ControlsListingCallback::class.java)
         val controlsServiceInfo = mock<ControlsServiceInfo>()
@@ -76,14 +74,14 @@
         verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
 
         captor.value.onServicesUpdated(serviceInfo)
-        backgroundExecutor.runAllReady()
-        uiExecutor.runAllReady()
+        FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
 
         assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
     }
 
     @Test
-    fun testOnServicesUpdatedDoesntHavePanels() {
+    fun testOnServicesUpdated_showsNotAuthorizedPanels() {
+        adapter = createAdapterWithAuthorizedPanels(emptySet())
         val captor = ArgumentCaptor
                 .forClass(ControlsListingController.ControlsListingCallback::class.java)
         val serviceInfo = listOf(
@@ -93,20 +91,88 @@
         verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
 
         captor.value.onServicesUpdated(serviceInfo)
-        backgroundExecutor.runAllReady()
-        uiExecutor.runAllReady()
+        FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
+
+        assertThat(adapter.itemCount).isEqualTo(2)
+    }
+
+    @Test
+    fun testOnServicesUpdated_doesntShowAuthorizedPanels() {
+        adapter = createAdapterWithAuthorizedPanels(setOf(TEST_PACKAGE))
+
+        val captor = ArgumentCaptor
+                .forClass(ControlsListingController.ControlsListingCallback::class.java)
+        val serviceInfo = listOf(
+                ControlsServiceInfo("no panel", null),
+                ControlsServiceInfo("panel", ComponentName(TEST_PACKAGE, "cls"))
+        )
+        verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
+
+        captor.value.onServicesUpdated(serviceInfo)
+        FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
 
         assertThat(adapter.itemCount).isEqualTo(1)
     }
 
-    fun ControlsServiceInfo(
-        label: CharSequence,
-        panelComponentName: ComponentName? = null
-    ): ControlsServiceInfo {
-        return mock {
-            `when`(this.loadLabel()).thenReturn(label)
-            `when`(this.panelActivity).thenReturn(panelComponentName)
-            `when`(this.loadIcon()).thenReturn(mock())
+    @Test
+    fun testOnBindSetsClickListenerToCallOnAppSelected() {
+        adapter = createAdapterWithAuthorizedPanels(emptySet())
+
+        val captor = ArgumentCaptor
+                .forClass(ControlsListingController.ControlsListingCallback::class.java)
+        val serviceInfo = listOf(
+                ControlsServiceInfo("no panel", null),
+                ControlsServiceInfo("panel", ComponentName(TEST_PACKAGE, "cls"))
+        )
+        verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
+
+        captor.value.onServicesUpdated(serviceInfo)
+        FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
+
+        val sorted = serviceInfo.sortedWith(
+                compareBy(Collator.getInstance(resources.configuration.locales[0])) {
+                    it.loadLabel() ?: ""
+                })
+
+        sorted.forEachIndexed { index, info ->
+            val fakeView: View = mock()
+            val fakeHolder: AppAdapter.Holder = mock()
+            `when`(fakeHolder.view).thenReturn(fakeView)
+
+            clearInvocations(onAppSelected)
+            adapter.onBindViewHolder(fakeHolder, index)
+            val listenerCaptor: ArgumentCaptor<View.OnClickListener> = argumentCaptor()
+            verify(fakeView).setOnClickListener(capture(listenerCaptor))
+            listenerCaptor.value.onClick(fakeView)
+
+            verify(onAppSelected).invoke(info)
         }
     }
+
+    private fun createAdapterWithAuthorizedPanels(packages: Set<String>): AppAdapter {
+        return AppAdapter(backgroundExecutor,
+                uiExecutor,
+                lifecycle,
+                controlsListingController,
+                layoutInflater,
+                onAppSelected,
+                favoritesRenderer,
+                resources,
+                packages)
+    }
+
+    companion object {
+        private fun ControlsServiceInfo(
+                label: CharSequence,
+                panelComponentName: ComponentName? = null
+        ): ControlsServiceInfo {
+            return mock {
+                `when`(loadLabel()).thenReturn(label)
+                `when`(panelActivity).thenReturn(panelComponentName)
+                `when`(loadIcon()).thenReturn(mock())
+            }
+        }
+
+        private const val TEST_PACKAGE = "package"
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index 56c3efe..8dfd223 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -16,7 +16,13 @@
 
 package com.android.systemui.controls.management
 
+import android.app.Dialog
+import android.content.ComponentName
 import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.graphics.drawable.Drawable
+import android.os.Bundle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.window.OnBackInvokedCallback
@@ -25,14 +31,23 @@
 import androidx.test.rule.ActivityTestRule
 import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
-import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.MoreExecutors
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
+import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -41,7 +56,11 @@
 import org.mockito.ArgumentMatchers
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -58,9 +77,10 @@
 
     @Mock lateinit var userTracker: UserTracker
 
-    @Mock lateinit var uiController: ControlsUiController
+    @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
 
-    private lateinit var controlsProviderSelectorActivity: ControlsProviderSelectorActivity_Factory
+    @Mock lateinit var dialogFactory: PanelConfirmationDialogFactory
+
     private var latch: CountDownLatch = CountDownLatch(1)
 
     @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
@@ -81,7 +101,8 @@
                         listingController,
                         controlsController,
                         userTracker,
-                        uiController,
+                        authorizedPanelsRepository,
+                        dialogFactory,
                         mockDispatcher,
                         latch
                     )
@@ -113,13 +134,99 @@
         verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
     }
 
-    public class TestableControlsProviderSelectorActivity(
+    @Test
+    fun testOnAppSelectedForNonPanelStartsFavoritingActivity() {
+        val info = ControlsServiceInfo(ComponentName("test_pkg", "service"), "", null)
+        activityRule.activity.onAppSelected(info)
+
+        verifyNoMoreInteractions(dialogFactory)
+
+        assertThat(activityRule.activity.lastStartedActivity?.component?.className)
+            .isEqualTo(ControlsFavoritingActivity::class.java.name)
+
+        assertThat(activityRule.activity.triedToFinish).isTrue()
+    }
+
+    @Test
+    fun testOnAppSelectedForPanelTriggersDialog() {
+        val label = "label"
+        val info =
+            ControlsServiceInfo(
+                ComponentName("test_pkg", "service"),
+                label,
+                ComponentName("test_pkg", "activity")
+            )
+
+        val dialog: Dialog = mock()
+        whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog)
+
+        activityRule.activity.onAppSelected(info)
+        verify(dialogFactory).createConfirmationDialog(any(), eq(label), any())
+        verify(dialog).show()
+
+        assertThat(activityRule.activity.triedToFinish).isFalse()
+    }
+
+    @Test
+    fun dialogAcceptAddsPackage() {
+        val label = "label"
+        val info =
+            ControlsServiceInfo(
+                ComponentName("test_pkg", "service"),
+                label,
+                ComponentName("test_pkg", "activity")
+            )
+
+        val dialog: Dialog = mock()
+        whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog)
+
+        activityRule.activity.onAppSelected(info)
+
+        val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor()
+        verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor))
+
+        captor.value.accept(true)
+
+        val setCaptor: ArgumentCaptor<Set<String>> = argumentCaptor()
+        verify(authorizedPanelsRepository).addAuthorizedPanels(capture(setCaptor))
+        assertThat(setCaptor.value).containsExactly(info.componentName.packageName)
+
+        assertThat(activityRule.activity.triedToFinish).isTrue()
+    }
+
+    @Test
+    fun dialogCancelDoesntAddPackage() {
+        val label = "label"
+        val info =
+            ControlsServiceInfo(
+                ComponentName("test_pkg", "service"),
+                label,
+                ComponentName("test_pkg", "activity")
+            )
+
+        val dialog: Dialog = mock()
+        whenever(dialogFactory.createConfirmationDialog(any(), any(), any())).thenReturn(dialog)
+
+        activityRule.activity.onAppSelected(info)
+
+        val captor: ArgumentCaptor<Consumer<Boolean>> = argumentCaptor()
+        verify(dialogFactory).createConfirmationDialog(any(), any(), capture(captor))
+
+        captor.value.accept(false)
+
+        verify(authorizedPanelsRepository, never()).addAuthorizedPanels(any())
+
+        assertThat(activityRule.activity.triedToFinish).isFalse()
+    }
+
+    class TestableControlsProviderSelectorActivity(
         executor: Executor,
         backExecutor: Executor,
         listingController: ControlsListingController,
         controlsController: ControlsController,
         userTracker: UserTracker,
-        uiController: ControlsUiController,
+        authorizedPanelsRepository: AuthorizedPanelsRepository,
+        dialogFactory: PanelConfirmationDialogFactory,
         private val mockDispatcher: OnBackInvokedDispatcher,
         private val latch: CountDownLatch
     ) :
@@ -129,16 +236,50 @@
             listingController,
             controlsController,
             userTracker,
-            uiController
+            authorizedPanelsRepository,
+            dialogFactory
         ) {
+
+        var lastStartedActivity: Intent? = null
+        var triedToFinish = false
+
         override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
             return mockDispatcher
         }
 
+        override fun startActivity(intent: Intent?, options: Bundle?) {
+            lastStartedActivity = intent
+        }
+
         override fun onStop() {
             super.onStop()
             // ensures that test runner thread does not proceed until ui thread is done
             latch.countDown()
         }
+
+        override fun animateExitAndFinish() {
+            // Activity should only be finished from the rule.
+            triedToFinish = true
+        }
+    }
+
+    companion object {
+        private fun ControlsServiceInfo(
+            componentName: ComponentName,
+            label: CharSequence,
+            panelComponentName: ComponentName? = null
+        ): ControlsServiceInfo {
+            val serviceInfo =
+                ServiceInfo().apply {
+                    applicationInfo = ApplicationInfo()
+                    packageName = componentName.packageName
+                    name = componentName.className
+                }
+            return Mockito.spy(ControlsServiceInfo(mock(), serviceInfo)).apply {
+                doReturn(label).`when`(this).loadLabel()
+                doReturn(mock<Drawable>()).`when`(this).loadIcon()
+                doReturn(panelComponentName).`when`(this).panelActivity
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
new file mode 100644
index 0000000..756f267
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.systemui.controls.management
+
+import android.content.DialogInterface
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class PanelConfirmationDialogFactoryTest : SysuiTestCase() {
+
+    @Test
+    fun testDialogHasCorrectInfo() {
+        val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
+        val factory = PanelConfirmationDialogFactory { mockDialog }
+        val appName = "appName"
+
+        factory.createConfirmationDialog(context, appName) {}
+
+        verify(mockDialog).setCanceledOnTouchOutside(true)
+        verify(mockDialog)
+            .setTitle(context.getString(R.string.controls_panel_authorization_title, appName))
+        verify(mockDialog)
+            .setMessage(context.getString(R.string.controls_panel_authorization, appName))
+    }
+
+    @Test
+    fun testDialogPositiveButton() {
+        val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
+        val factory = PanelConfirmationDialogFactory { mockDialog }
+
+        var response: Boolean? = null
+
+        factory.createConfirmationDialog(context, "") { response = it }
+
+        val captor: ArgumentCaptor<DialogInterface.OnClickListener> = argumentCaptor()
+        verify(mockDialog).setPositiveButton(eq(R.string.controls_dialog_ok), capture(captor))
+
+        captor.value.onClick(mockDialog, DialogInterface.BUTTON_POSITIVE)
+
+        assertThat(response).isTrue()
+    }
+
+    @Test
+    fun testDialogNeutralButton() {
+        val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
+        val factory = PanelConfirmationDialogFactory { mockDialog }
+
+        var response: Boolean? = null
+
+        factory.createConfirmationDialog(context, "") { response = it }
+
+        val captor: ArgumentCaptor<DialogInterface.OnClickListener> = argumentCaptor()
+        verify(mockDialog).setNeutralButton(eq(R.string.cancel), capture(captor))
+
+        captor.value.onClick(mockDialog, DialogInterface.BUTTON_NEUTRAL)
+
+        assertThat(response).isFalse()
+    }
+
+    @Test
+    fun testDialogCancel() {
+        val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
+        val factory = PanelConfirmationDialogFactory { mockDialog }
+
+        var response: Boolean? = null
+
+        factory.createConfirmationDialog(context, "") { response = it }
+
+        val captor: ArgumentCaptor<DialogInterface.OnCancelListener> = argumentCaptor()
+        verify(mockDialog).setOnCancelListener(capture(captor))
+
+        captor.value.onCancel(mockDialog)
+
+        assertThat(response).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
new file mode 100644
index 0000000..b91a3fd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.systemui.controls.panels
+
+import android.content.SharedPreferences
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class AuthorizedPanelsRepositoryImplTest : SysuiTestCase() {
+
+    @Mock private lateinit var userTracker: UserTracker
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mContext.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf<String>()
+        )
+        whenever(userTracker.userId).thenReturn(0)
+    }
+
+    @Test
+    fun testPreApprovedPackagesAreSeededIfNoSavedPreferences() {
+        mContext.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(TEST_PACKAGE)
+        )
+        val sharedPrefs = FakeSharedPreferences()
+        val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+        val repository = createRepository(fileManager)
+
+        assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
+        assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE)
+    }
+
+    @Test
+    fun testPreApprovedPackagesNotSeededIfEmptySavedPreferences() {
+        mContext.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(TEST_PACKAGE)
+        )
+        val sharedPrefs = FakeSharedPreferences()
+        sharedPrefs.edit().putStringSet(KEY, emptySet()).apply()
+        val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+        createRepository(fileManager)
+
+        assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
+    }
+
+    @Test
+    fun testPreApprovedPackagesOnlySetForUserThatDoesntHaveThem() {
+        mContext.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(TEST_PACKAGE)
+        )
+        val sharedPrefs_0 = FakeSharedPreferences()
+        val sharedPrefs_1 = FakeSharedPreferences()
+        sharedPrefs_1.edit().putStringSet(KEY, emptySet()).apply()
+        val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs_0, 1 to sharedPrefs_1))
+        val repository = createRepository(fileManager)
+
+        assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
+        whenever(userTracker.userId).thenReturn(1)
+        assertThat(repository.getAuthorizedPanels()).isEmpty()
+    }
+
+    @Test
+    fun testGetAuthorizedPackages() {
+        val sharedPrefs = FakeSharedPreferences()
+        sharedPrefs.edit().putStringSet(KEY, mutableSetOf(TEST_PACKAGE)).apply()
+        val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+
+        val repository = createRepository(fileManager)
+        assertThat(repository.getAuthorizedPanels()).containsExactly(TEST_PACKAGE)
+    }
+
+    @Test
+    fun testSetAuthorizedPackage() {
+        val sharedPrefs = FakeSharedPreferences()
+        val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+
+        val repository = createRepository(fileManager)
+        repository.addAuthorizedPanels(setOf(TEST_PACKAGE))
+        assertThat(sharedPrefs.getStringSet(KEY, null)).containsExactly(TEST_PACKAGE)
+    }
+
+    private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
+        return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
+    }
+
+    private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
+        UserFileManager {
+        override fun getFile(fileName: String, userId: Int): File {
+            throw UnsupportedOperationException()
+        }
+
+        override fun getSharedPreferences(
+            fileName: String,
+            mode: Int,
+            userId: Int
+        ): SharedPreferences {
+            if (fileName != FILE_NAME) {
+                throw IllegalArgumentException("Preference files must be $FILE_NAME")
+            }
+            return sharedPrefs.getValue(userId)
+        }
+    }
+
+    companion object {
+        private const val FILE_NAME = "controls_prefs"
+        private const val KEY = "authorized_panels"
+        private const val TEST_PACKAGE = "package"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
new file mode 100644
index 0000000..7ecaca6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -0,0 +1,281 @@
+/*
+ * 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.systemui.controls.start
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsStartableTest : SysuiTestCase() {
+
+    @Mock private lateinit var controlsController: ControlsController
+    @Mock private lateinit var controlsListingController: ControlsListingController
+    @Mock private lateinit var userTracker: UserTracker
+
+    private lateinit var fakeExecutor: FakeExecutor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        context.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf<String>()
+        )
+
+        fakeExecutor = FakeExecutor(FakeSystemClock())
+    }
+
+    @Test
+    fun testDisabledNothingIsCalled() {
+        createStartable(enabled = false).start()
+
+        verifyZeroInteractions(controlsController, controlsListingController, userTracker)
+    }
+
+    @Test
+    fun testNoPreferredPackagesNoDefaultSelected_noNewSelection() {
+        `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController, never()).setPreferredSelection(any())
+    }
+
+    @Test
+    fun testPreferredPackagesNotInstalled_noNewSelection() {
+        context.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(TEST_PACKAGE_PANEL)
+        )
+        `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+        `when`(controlsListingController.getCurrentServices()).thenReturn(emptyList())
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController, never()).setPreferredSelection(any())
+    }
+
+    @Test
+    fun testPreferredPackageNotPanel_noNewSelection() {
+        context.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(TEST_PACKAGE_PANEL)
+        )
+        `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController, never()).setPreferredSelection(any())
+    }
+
+    @Test
+    fun testExistingSelection_noNewSelection() {
+        context.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(TEST_PACKAGE_PANEL)
+        )
+        `when`(controlsController.getPreferredSelection())
+            .thenReturn(mock<SelectedItem.PanelItem>())
+        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController, never()).setPreferredSelection(any())
+    }
+
+    @Test
+    fun testPanelAdded() {
+        context.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(TEST_PACKAGE_PANEL)
+        )
+        `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
+    }
+
+    @Test
+    fun testMultiplePreferredOnlyOnePanel_panelAdded() {
+        context.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf("other_package", TEST_PACKAGE_PANEL)
+        )
+        `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+        val listings =
+            listOf(
+                ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true),
+                ControlsServiceInfo(ComponentName("other_package", "cls"), "non panel", false)
+            )
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
+    }
+
+    @Test
+    fun testMultiplePreferredMultiplePanels_firstPreferredAdded() {
+        context.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(TEST_PACKAGE_PANEL, "other_package")
+        )
+        `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+        val listings =
+            listOf(
+                ControlsServiceInfo(ComponentName("other_package", "cls"), "panel", true),
+                ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)
+            )
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController).setPreferredSelection(listings[1].toPanelItem())
+    }
+
+    @Test
+    fun testPreferredSelectionIsPanel_bindOnStart() {
+        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+        `when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL)
+    }
+
+    @Test
+    fun testPreferredSelectionPanel_listingNoPanel_notBind() {
+        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+        `when`(controlsController.getPreferredSelection())
+            .thenReturn(SelectedItem.PanelItem("panel", TEST_COMPONENT_PANEL))
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController, never()).bindComponentForPanel(any())
+    }
+
+    @Test
+    fun testNotPanelSelection_noBind() {
+        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+        `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController, never()).bindComponentForPanel(any())
+    }
+
+    private fun createStartable(enabled: Boolean): ControlsStartable {
+        val component: ControlsComponent =
+            mock() {
+                `when`(isEnabled()).thenReturn(enabled)
+                if (enabled) {
+                    `when`(getControlsController()).thenReturn(Optional.of(controlsController))
+                    `when`(getControlsListingController())
+                        .thenReturn(Optional.of(controlsListingController))
+                } else {
+                    `when`(getControlsController()).thenReturn(Optional.empty())
+                    `when`(getControlsListingController()).thenReturn(Optional.empty())
+                }
+            }
+        return ControlsStartable(context.resources, fakeExecutor, component, userTracker)
+    }
+
+    private fun ControlsServiceInfo(
+        componentName: ComponentName,
+        label: CharSequence,
+        hasPanel: Boolean
+    ): ControlsServiceInfo {
+        val serviceInfo =
+            ServiceInfo().apply {
+                applicationInfo = ApplicationInfo()
+                packageName = componentName.packageName
+                name = componentName.className
+            }
+        return FakeControlsServiceInfo(context, serviceInfo, label, hasPanel)
+    }
+
+    private class FakeControlsServiceInfo(
+        context: Context,
+        serviceInfo: ServiceInfo,
+        private val label: CharSequence,
+        hasPanel: Boolean
+    ) : ControlsServiceInfo(context, serviceInfo) {
+
+        init {
+            if (hasPanel) {
+                panelActivity = serviceInfo.componentName
+            }
+        }
+
+        override fun loadLabel(): CharSequence {
+            return label
+        }
+    }
+
+    companion object {
+        private fun ControlsServiceInfo.toPanelItem(): SelectedItem.PanelItem {
+            if (panelActivity == null) {
+                throw IllegalArgumentException("$this is not a panel")
+            }
+            return SelectedItem.PanelItem(loadLabel(), componentName)
+        }
+
+        private const val TEST_PACKAGE = "pkg"
+        private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
+        private const val TEST_PACKAGE_PANEL = "pkg.panel"
+        private val TEST_COMPONENT_PANEL = ComponentName(TEST_PACKAGE_PANEL, "service")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index edc6882..85f9961 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -20,6 +20,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
 import android.content.pm.ServiceInfo
 import android.os.UserHandle
 import android.service.controls.ControlsProviderService
@@ -39,8 +40,10 @@
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
@@ -91,6 +94,9 @@
     @Mock lateinit var userTracker: UserTracker
     @Mock lateinit var taskViewFactory: TaskViewFactory
     @Mock lateinit var dumpManager: DumpManager
+    @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
+    @Mock lateinit var featureFlags: FeatureFlags
+    @Mock lateinit var packageManager: PackageManager
     val sharedPreferences = FakeSharedPreferences()
     lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
 
@@ -120,6 +126,7 @@
             ControlsUiControllerImpl(
                 Lazy { controlsController },
                 context,
+                packageManager,
                 uiExecutor,
                 bgExecutor,
                 Lazy { controlsListingController },
@@ -132,6 +139,8 @@
                 userTracker,
                 Optional.of(taskViewFactory),
                 controlsSettingsRepository,
+                authorizedPanelsRepository,
+                featureFlags,
                 dumpManager
             )
         `when`(
@@ -240,7 +249,9 @@
     @Test
     fun testPanelCallsTaskViewFactoryCreate() {
         mockLayoutInflater()
-        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val packageName = "pkg"
+        `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
+        val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
         val serviceInfo = setUpPanel(panel)
 
         underTest.show(parent, {}, context)
@@ -258,9 +269,11 @@
     @Test
     fun testPanelControllerStartActivityWithCorrectArguments() {
         mockLayoutInflater()
+        val packageName = "pkg"
+        `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
         controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
 
-        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
         val serviceInfo = setUpPanel(panel)
 
         underTest.show(parent, {}, context)
@@ -290,9 +303,11 @@
     @Test
     fun testPendingIntentExtrasAreModified() {
         mockLayoutInflater()
+        val packageName = "pkg"
+        `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName))
         controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
 
-        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls"))
         val serviceInfo = setUpPanel(panel)
 
         underTest.show(parent, {}, context)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.kt
new file mode 100644
index 0000000..dbaf94f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/OverflowMenuAdapterTest.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.systemui.controls.ui
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class OverflowMenuAdapterTest : SysuiTestCase() {
+
+    @Test
+    fun testGetItemId() {
+        val ids = listOf(27L, 73L)
+        val labels = listOf("first", "second")
+        val adapter =
+            OverflowMenuAdapter(
+                context,
+                layoutId = 0,
+                labels.zip(ids).map { OverflowMenuAdapter.MenuItem(it.first, it.second) }
+            ) { true }
+
+        ids.forEachIndexed { index, id -> assertThat(adapter.getItemId(index)).isEqualTo(id) }
+    }
+
+    @Test
+    fun testCheckEnabled() {
+        val ids = listOf(27L, 73L)
+        val labels = listOf("first", "second")
+        val adapter =
+            OverflowMenuAdapter(
+                context,
+                layoutId = 0,
+                labels.zip(ids).map { OverflowMenuAdapter.MenuItem(it.first, it.second) }
+            ) { position -> position == 0 }
+
+        assertThat(adapter.isEnabled(0)).isTrue()
+        assertThat(adapter.isEnabled(1)).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index 5c2b153..af027e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -36,6 +37,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerFake;
 import com.android.systemui.doze.DozeMachine.State;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,6 +50,7 @@
 @RunWithLooper
 public class DozeDockHandlerTest extends SysuiTestCase {
     @Mock private DozeMachine mMachine;
+    @Mock private UserTracker mUserTracker;
     private AmbientDisplayConfiguration mConfig;
     private DockManagerFake mDockManagerFake;
     private DozeDockHandler mDockHandler;
@@ -57,9 +60,10 @@
         MockitoAnnotations.initMocks(this);
         mConfig = DozeConfigurationUtil.createMockConfig();
         mDockManagerFake = spy(new DockManagerFake());
-        mDockHandler = new DozeDockHandler(mConfig, mDockManagerFake);
+        mDockHandler = new DozeDockHandler(mConfig, mDockManagerFake, mUserTracker);
         mDockHandler.setDozeMachine(mMachine);
 
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
         when(mMachine.getState()).thenReturn(State.DOZE_AOD);
         doReturn(true).when(mConfig).alwaysOnEnabled(anyInt());
         mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 5bbd810..a636b7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -45,6 +45,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.testing.AndroidTestingRunner;
@@ -57,6 +58,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
@@ -85,6 +87,8 @@
     private DozeMachine.Part mPartMock;
     @Mock
     private DozeMachine.Part mAnotherPartMock;
+    @Mock
+    private UserTracker mUserTracker;
     private DozeServiceFake mServiceFake;
     private WakeLockFake mWakeLockFake;
     private AmbientDisplayConfiguration mAmbientDisplayConfigMock;
@@ -97,6 +101,7 @@
         mAmbientDisplayConfigMock = mock(AmbientDisplayConfiguration.class);
         when(mDockManager.isDocked()).thenReturn(false);
         when(mDockManager.isHidden()).thenReturn(false);
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
 
         mMachine = new DozeMachine(mServiceFake,
                 mAmbientDisplayConfigMock,
@@ -105,7 +110,8 @@
                 mDozeLog,
                 mDockManager,
                 mHost,
-                new DozeMachine.Part[]{mPartMock, mAnotherPartMock});
+                new DozeMachine.Part[]{mPartMock, mAnotherPartMock},
+                mUserTracker);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 03827da..3af444a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -34,6 +34,7 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.reset;
@@ -57,6 +58,7 @@
 import com.android.systemui.util.concurrency.FakeThreadFactory;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.FakeSensorManager;
+import com.android.systemui.util.settings.SystemSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -94,6 +96,8 @@
     DevicePostureController mDevicePostureController;
     @Mock
     DozeLog mDozeLog;
+    @Mock
+    SystemSettings mSystemSettings;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
 
@@ -102,9 +106,8 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        Settings.System.putIntForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS, DEFAULT_BRIGHTNESS,
-                UserHandle.USER_CURRENT);
+        when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(),
+                eq(UserHandle.USER_CURRENT))).thenReturn(DEFAULT_BRIGHTNESS);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
             return null;
@@ -131,7 +134,8 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog);
+                mDozeLog,
+                mSystemSettings);
     }
 
     @Test
@@ -157,11 +161,10 @@
     }
 
     @Test
-    public void testAod_usesLightSensorRespectingUserSetting() throws Exception {
+    public void testAod_usesLightSensorRespectingUserSetting() {
         int maxBrightness = 3;
-        Settings.System.putIntForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS, maxBrightness,
-                UserHandle.USER_CURRENT);
+        when(mSystemSettings.getIntForUser(eq(Settings.System.SCREEN_BRIGHTNESS), anyInt(),
+                eq(UserHandle.USER_CURRENT))).thenReturn(maxBrightness);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         assertEquals(maxBrightness, mServiceFake.screenBrightness);
@@ -238,7 +241,8 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog);
+                mDozeLog,
+                mSystemSettings);
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
         reset(mDozeHost);
@@ -275,7 +279,8 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog);
+                mDozeLog,
+                mSystemSettings);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
@@ -306,7 +311,8 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog);
+                mDozeLog,
+                mSystemSettings);
 
         // GIVEN the device is in AOD
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
@@ -344,7 +350,8 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog);
+                mDozeLog,
+                mSystemSettings);
 
         // GIVEN device is in AOD
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
@@ -386,7 +393,8 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog);
+                mDozeLog,
+                mSystemSettings);
         verify(mDevicePostureController).addCallback(postureCallbackCaptor.capture());
 
         // GIVEN device is in AOD
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index d910a27..986d6d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -35,11 +35,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -120,11 +120,13 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
         when(mAmbientDisplayConfiguration.tapSensorTypeMapping())
                 .thenReturn(new String[]{"tapSensor"});
         when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
         when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
-        when(mAmbientDisplayConfiguration.enabled(UserHandle.USER_CURRENT)).thenReturn(true);
+        when(mAmbientDisplayConfiguration.enabled(ActivityManager.getCurrentUser())).thenReturn(
+                true);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
             return null;
@@ -144,7 +146,7 @@
 
     @Test
     public void testSensorDebounce() {
-        mDozeSensors.setListening(true, true);
+        mDozeSensors.setListening(true, true, true);
 
         mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
         mTestableLooper.processAllMessages();
@@ -162,7 +164,7 @@
     @Test
     public void testSetListening_firstTrue_registerSettingsObserver() {
         verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
-        mDozeSensors.setListening(true, true);
+        mDozeSensors.setListening(true, true, true);
 
         verify(mTriggerSensor).registerSettingsObserver(any(ContentObserver.class));
     }
@@ -170,8 +172,8 @@
     @Test
     public void testSetListening_twiceTrue_onlyRegisterSettingsObserverOnce() {
         verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
-        mDozeSensors.setListening(true, true);
-        mDozeSensors.setListening(true, true);
+        mDozeSensors.setListening(true, true, true);
+        mDozeSensors.setListening(true, true, true);
 
         verify(mTriggerSensor, times(1)).registerSettingsObserver(any(ContentObserver.class));
     }
@@ -196,7 +198,7 @@
         assertFalse(mSensorTap.mRequested);
 
         // WHEN we're now in a low powered state
-        dozeSensors.setListening(true, true, true);
+        dozeSensors.setListeningWithPowerState(true, true, true, true);
 
         // THEN the tap sensor is registered
         assertTrue(mSensorTap.mRequested);
@@ -207,12 +209,12 @@
         // GIVEN doze sensors enabled
         when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
 
-        // GIVEN a trigger sensor
+        // GIVEN a trigger sensor that's enabled by settings
         Sensor mockSensor = mock(Sensor.class);
-        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensorWithSettingEnabled(
                 mockSensor,
-                /* settingEnabled */ true,
-                /* requiresTouchScreen */ true);
+                /* settingEnabled */ true
+        );
         when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
                 .thenReturn(true);
 
@@ -228,12 +230,12 @@
         // GIVEN doze sensors enabled
         when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
 
-        // GIVEN a trigger sensor
+        // GIVEN a trigger sensor that's not enabled by settings
         Sensor mockSensor = mock(Sensor.class);
-        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensorWithSettingEnabled(
                 mockSensor,
-                /* settingEnabled*/ false,
-                /* requiresTouchScreen */ true);
+                /* settingEnabled*/ false
+        );
         when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
                 .thenReturn(true);
 
@@ -249,12 +251,12 @@
         // GIVEN doze sensors enabled
         when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
 
-        // GIVEN a trigger sensor that's
+        // GIVEN a trigger sensor that's not enabled by settings
         Sensor mockSensor = mock(Sensor.class);
-        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensorWithSettingEnabled(
                 mockSensor,
-                /* settingEnabled*/ false,
-                /* requiresTouchScreen */ true);
+                /* settingEnabled*/ false
+        );
         when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
                 .thenReturn(true);
 
@@ -264,7 +266,7 @@
         // WHEN ignoreSetting is called
         triggerSensor.ignoreSetting(true);
 
-        // THEN the sensor is registered
+        // THEN the sensor is still registered since the setting is ignore
         assertTrue(triggerSensor.mRegistered);
     }
 
@@ -275,10 +277,10 @@
 
         // GIVEN a trigger sensor
         Sensor mockSensor = mock(Sensor.class);
-        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensorWithSettingEnabled(
                 mockSensor,
-                /* settingEnabled*/ true,
-                /* requiresTouchScreen */ true);
+                /* settingEnabled*/ true
+        );
         when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
                 .thenReturn(true);
 
@@ -295,7 +297,7 @@
         // GIVEN doze sensor that supports postures
         Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
         Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
-        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensorForPosture(
                 new Sensor[] {
                         null /* unknown */,
                         closedSensor,
@@ -316,7 +318,7 @@
         // GIVEN doze sensor that supports postures
         Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
         Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
-        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensorForPosture(
                 new Sensor[] {
                         null /* unknown */,
                         closedSensor,
@@ -345,7 +347,7 @@
         // GIVEN doze sensor that supports postures
         Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
         Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT);
-        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensorForPosture(
                 new Sensor[] {
                         null /* unknown */,
                         closedSensor,
@@ -400,7 +402,7 @@
     public void testUdfpsEnrollmentChanged() throws Exception {
         // GIVEN a UDFPS_LONG_PRESS trigger sensor that's not configured
         Sensor mockSensor = mock(Sensor.class);
-        TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+        TriggerSensor triggerSensor = mDozeSensors.createDozeSensorForPosture(
                 mockSensor,
                 REASON_SENSOR_UDFPS_LONG_PRESS,
                 /* configured */ false);
@@ -409,7 +411,7 @@
                 .thenReturn(true);
 
         // WHEN listening state is set to TRUE
-        mDozeSensors.setListening(true, true);
+        mDozeSensors.setListening(true, true, true);
 
         // THEN mRegistered is still false b/c !mConfigured
         assertFalse(triggerSensor.mConfigured);
@@ -439,6 +441,35 @@
     }
 
     @Test
+    public void aodOnlySensor_onlyRegisteredWhenAodSensorsIncluded() {
+        // GIVEN doze sensors enabled
+        when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+        // GIVEN a trigger sensor that requires aod
+        Sensor mockSensor = mock(Sensor.class);
+        TriggerSensor aodOnlyTriggerSensor = mDozeSensors.createDozeSensorRequiringAod(mockSensor);
+        when(mSensorManager.requestTriggerSensor(eq(aodOnlyTriggerSensor), eq(mockSensor)))
+                .thenReturn(true);
+        mDozeSensors.addSensor(aodOnlyTriggerSensor);
+
+        // WHEN aod only sensors aren't included
+        mDozeSensors.setListening(/* listen */ true, /* includeTouchScreenSensors */true,
+                /* includeAodOnlySensors */false);
+
+        // THEN the sensor is not registered or requested
+        assertFalse(aodOnlyTriggerSensor.mRequested);
+        assertFalse(aodOnlyTriggerSensor.mRegistered);
+
+        // WHEN aod only sensors ARE included
+        mDozeSensors.setListening(/* listen */ true, /* includeTouchScreenSensors */true,
+                /* includeAodOnlySensors */true);
+
+        // THEN the sensor is registered and requested
+        assertTrue(aodOnlyTriggerSensor.mRequested);
+        assertTrue(aodOnlyTriggerSensor.mRegistered);
+    }
+
+    @Test
     public void liftToWake_defaultSetting_configDefaultFalse() {
         // WHEN the default lift to wake gesture setting is false
         when(mResources.getBoolean(
@@ -494,8 +525,8 @@
             mTriggerSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
         }
 
-        public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled,
-                boolean requiresTouchScreen) {
+        public TriggerSensor createDozeSensorWithSettingEnabled(Sensor sensor,
+                boolean settingEnabled) {
             return new TriggerSensor(/* sensor */ sensor,
                     /* setting name */ "test_setting",
                     /* settingDefault */ settingEnabled,
@@ -504,11 +535,13 @@
                     /* reportsTouchCoordinate*/ false,
                     /* requiresTouchscreen */ false,
                     /* ignoresSetting */ false,
-                    requiresTouchScreen,
-                    /* immediatelyReRegister */ true);
+                    /* requiresProx */ false,
+                    /* immediatelyReRegister */ true,
+                    /* requiresAod */false
+            );
         }
 
-        public TriggerSensor createDozeSensor(
+        public TriggerSensor createDozeSensorForPosture(
                 Sensor sensor,
                 int pulseReason,
                 boolean configured
@@ -522,15 +555,35 @@
                     /* requiresTouchscreen */ false,
                     /* ignoresSetting */ false,
                     /* requiresTouchScreen */ false,
-                    /* immediatelyReRegister*/ true);
+                    /* immediatelyReRegister*/ true,
+                    false
+            );
         }
 
         /**
-         * create a doze sensor that supports postures and is enabled
+         * Create a doze sensor that requires Aod
          */
-        public TriggerSensor createDozeSensor(Sensor[] sensors, int posture) {
+        public TriggerSensor createDozeSensorRequiringAod(Sensor sensor) {
+            return new TriggerSensor(/* sensor */ sensor,
+                    /* setting name */ "aod_requiring_sensor",
+                    /* settingDefault */ true,
+                    /* configured */ true,
+                    /* pulseReason*/ 0,
+                    /* reportsTouchCoordinate*/ false,
+                    /* requiresTouchscreen */ false,
+                    /* ignoresSetting */ false,
+                    /* requiresProx */ false,
+                    /* immediatelyReRegister */ true,
+                    /* requiresAoD */ true
+            );
+        }
+
+        /**
+         * Create a doze sensor that supports postures and is enabled
+         */
+        public TriggerSensor createDozeSensorForPosture(Sensor[] sensors, int posture) {
             return new TriggerSensor(/* sensor */ sensors,
-                    /* setting name */ "test_setting",
+                    /* setting name */ "posture_test_setting",
                     /* settingDefault */ true,
                     /* configured */ true,
                     /* pulseReason*/ 0,
@@ -539,7 +592,9 @@
                     /* ignoresSetting */ true,
                     /* requiresProx */ false,
                     /* immediatelyReRegister */ true,
-                    posture);
+                    posture,
+                    /* requiresUi */ false
+            );
         }
 
         public void addSensor(TriggerSensor sensor) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
index 32b9945..9064470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.testing.AndroidTestingRunner;
 import android.testing.UiThreadTest;
@@ -43,6 +44,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 
 import org.junit.After;
@@ -73,6 +75,8 @@
     private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     @Mock
     private BiometricUnlockController mBiometricUnlockController;
+    @Mock
+    private UserTracker mUserTracker;
 
     @Mock
     private DozeMachine mDozeMachine;
@@ -89,12 +93,14 @@
         when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
         when(mBiometricUnlockController.hasPendingAuthentication()).thenReturn(false);
         when(mDozeHost.isProvisioned()).thenReturn(true);
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
 
         mDozeSuppressor = new DozeSuppressor(
                 mDozeHost,
                 mConfig,
                 mDozeLog,
-                mBiometricUnlockControllerLazy);
+                mBiometricUnlockControllerLazy,
+                mUserTracker);
 
         mDozeSuppressor.setDozeMachine(mDozeMachine);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index b66a454..3552399 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -395,6 +395,14 @@
         verify(mAuthController).onAodInterrupt(anyInt(), anyInt(), anyFloat(), anyFloat());
     }
 
+
+    @Test
+    public void udfpsLongPress_dozeState_notRegistered() {
+        // GIVEN device is DOZE_AOD_PAUSED
+        when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+        // beverlyt
+    }
+
     private void waitForSensorManager() {
         mExecutor.runAllReady();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 2799a25..6b095ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -36,11 +36,10 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.complication.ComplicationHostViewController;
+import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.statusbar.BlurUtils;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -81,12 +80,6 @@
     BlurUtils mBlurUtils;
 
     @Mock
-    StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-
-    @Mock
-    KeyguardBouncer mBouncer;
-
-    @Mock
     ViewRootImpl mViewRoot;
 
     @Mock
@@ -96,6 +89,9 @@
     DreamOverlayAnimationsController mAnimationsController;
 
     @Mock
+    BouncerlessScrimController mBouncerlessScrimController;
+
+    @Mock
     DreamOverlayStateController mStateController;
 
     DreamOverlayContainerViewController mController;
@@ -106,7 +102,6 @@
 
         when(mDreamOverlayContainerView.getResources()).thenReturn(mResources);
         when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
-        when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(mBouncer);
         when(mDreamOverlayContainerView.getViewRootImpl()).thenReturn(mViewRoot);
 
         mController = new DreamOverlayContainerViewController(
@@ -114,7 +109,6 @@
                 mComplicationHostViewController,
                 mDreamOverlayContentView,
                 mDreamOverlayStatusBarViewController,
-                mStatusBarKeyguardViewManager,
                 mBlurUtils,
                 mHandler,
                 mResources,
@@ -123,7 +117,8 @@
                 MILLIS_UNTIL_FULL_JITTER,
                 mPrimaryBouncerCallbackInteractor,
                 mAnimationsController,
-                mStateController);
+                mStateController,
+                mBouncerlessScrimController);
     }
 
     @Test
@@ -170,7 +165,8 @@
         final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
                 ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
         mController.onViewAttached();
-        verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
+        verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback(
+                bouncerExpansionCaptor.capture());
 
         bouncerExpansionCaptor.getValue().onExpansionChanged(0.5f);
         verify(mBlurUtils, never()).applyBlur(eq(mViewRoot), anyInt(), eq(false));
@@ -181,7 +177,8 @@
         final ArgumentCaptor<PrimaryBouncerExpansionCallback> bouncerExpansionCaptor =
                 ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
         mController.onViewAttached();
-        verify(mBouncer).addBouncerExpansionCallback(bouncerExpansionCaptor.capture());
+        verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback(
+                bouncerExpansionCaptor.capture());
 
         final float blurRadius = 1337f;
         when(mBlurUtils.blurRadiusOfRatio(anyFloat())).thenReturn(blurRadius);
@@ -217,6 +214,27 @@
     }
 
     @Test
+    public void testSkipEntryAnimationsWhenExitingLowLight() {
+        ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+        when(mStateController.isLowLightActive()).thenReturn(false);
+
+        // Call onInit so that the callback is added.
+        mController.onInit();
+        verify(mStateController).addCallback(callbackCaptor.capture());
+
+        // Send the signal that low light is exiting
+        callbackCaptor.getValue().onExitLowLight();
+
+        // View is attached to trigger animations.
+        mController.onViewAttached();
+
+        // Entry animations should be started then immediately ended to skip to the end.
+        verify(mAnimationsController).startEntryAnimations();
+        verify(mAnimationsController).endAnimations();
+    }
+
+    @Test
     public void testCancelDreamEntryAnimationsOnDetached() {
         mController.onViewAttached();
         mController.onViewDetached();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 4568d1e..dfb4d5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -31,6 +31,8 @@
 import android.os.RemoteException;
 import android.service.dreams.IDreamOverlay;
 import android.service.dreams.IDreamOverlayCallback;
+import android.service.dreams.IDreamOverlayClient;
+import android.service.dreams.IDreamOverlayClientCallback;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
 import android.view.ViewGroup;
@@ -58,6 +60,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
@@ -148,13 +151,25 @@
                 mDreamOverlayCallbackController);
     }
 
-    @Test
-    public void testOnStartMetricsLogged() throws Exception {
+    public IDreamOverlayClient getClient() throws RemoteException {
         final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClientCallback callback =
+                Mockito.mock(IDreamOverlayClientCallback.class);
+        overlay.getClient(callback);
+        final ArgumentCaptor<IDreamOverlayClient> clientCaptor =
+                ArgumentCaptor.forClass(IDreamOverlayClient.class);
+        verify(callback).onDreamOverlayClient(clientCaptor.capture());
+
+        return clientCaptor.getValue();
+    }
+
+    @Test
+    public void testOnStartMetricsLogged() throws Exception {
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -165,11 +180,10 @@
 
     @Test
     public void testOverlayContainerViewAddedToWindow() throws Exception {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -178,11 +192,10 @@
 
     @Test
     public void testDreamOverlayContainerViewControllerInitialized() throws Exception {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -196,11 +209,10 @@
                 .thenReturn(mDreamOverlayContainerViewParent)
                 .thenReturn(null);
 
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -209,11 +221,10 @@
 
     @Test
     public void testShouldShowComplicationsSetByStartDream() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 true /*shouldShowComplication*/);
 
         assertThat(mService.shouldShowComplications()).isTrue();
@@ -221,11 +232,10 @@
 
     @Test
     public void testLowLightSetByStartDream() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback,
+        client.startDream(mWindowParams, mDreamOverlayCallback,
                 LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -234,12 +244,36 @@
     }
 
     @Test
-    public void testDestroy() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+    public void testOnEndDream() throws RemoteException {
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback,
+        client.startDream(mWindowParams, mDreamOverlayCallback,
+                LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
+        mMainExecutor.runAllReady();
+
+        // Verify view added.
+        verify(mWindowManager).addView(mViewCaptor.capture(), any());
+
+        // Service destroyed.
+        mService.onEndDream();
+        mMainExecutor.runAllReady();
+
+        // Verify view removed.
+        verify(mWindowManager).removeView(mViewCaptor.getValue());
+
+        // Verify state correctly set.
+        verify(mStateController).setOverlayActive(false);
+        verify(mStateController).setLowLightActive(false);
+        verify(mStateController).setEntryAnimationsFinished(false);
+    }
+
+    @Test
+    public void testDestroy() throws RemoteException {
+        final IDreamOverlayClient client = getClient();
+
+        // Inform the overlay service of dream starting.
+        client.startDream(mWindowParams, mDreamOverlayCallback,
                 LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -279,15 +313,14 @@
 
     @Test
     public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Destroy the service.
         mService.onDestroy();
         mMainExecutor.runAllReady();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -305,11 +338,10 @@
 
     @Test
     public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting. Do not show dream complications.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -326,7 +358,7 @@
         // New dream starting with dream complications showing. Note that when a new dream is
         // binding to the dream overlay service, it receives the same instance of IBinder as the
         // first one.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 true /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
@@ -345,11 +377,10 @@
 
     @Test
     public void testWakeUp() throws RemoteException {
-        final IBinder proxy = mService.onBind(new Intent());
-        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        final IDreamOverlayClient client = getClient();
 
         // Inform the overlay service of dream starting.
-        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+        client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 true /*shouldShowComplication*/);
         mMainExecutor.runAllReady();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index ee989d1..b7d0f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -251,6 +251,30 @@
     }
 
     @Test
+    public void testNotifyLowLightExit() {
+        final DreamOverlayStateController stateController =
+                new DreamOverlayStateController(mExecutor, true);
+
+        stateController.addCallback(mCallback);
+        mExecutor.runAllReady();
+        assertThat(stateController.isLowLightActive()).isFalse();
+
+        // Turn low light on then off to trigger the exiting callback.
+        stateController.setLowLightActive(true);
+        stateController.setLowLightActive(false);
+
+        // Callback was only called once, when
+        mExecutor.runAllReady();
+        verify(mCallback, times(1)).onExitLowLight();
+        assertThat(stateController.isLowLightActive()).isFalse();
+
+        // Set with false again, which should not cause the callback to trigger again.
+        stateController.setLowLightActive(false);
+        mExecutor.runAllReady();
+        verify(mCallback, times(1)).onExitLowLight();
+    }
+
+    @Test
     public void testNotifyEntryAnimationsFinishedChanged() {
         final DreamOverlayStateController stateController =
                 new DreamOverlayStateController(mExecutor, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 85c2819..596b903 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.content.Context;
 import android.content.res.Resources;
@@ -47,6 +48,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -109,6 +111,8 @@
     View mStatusBarItemView;
     @Mock
     DreamOverlayStateController mDreamOverlayStateController;
+    @Mock
+    UserTracker mUserTracker;
 
     @Captor
     private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
@@ -125,6 +129,7 @@
                 .thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING);
         doCallRealMethod().when(mView).setVisibility(anyInt());
         doCallRealMethod().when(mView).getVisibility();
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
 
         mController = new DreamOverlayStatusBarViewController(
                 mView,
@@ -140,7 +145,8 @@
                 mZenModeController,
                 mStatusBarWindowStateController,
                 mDreamOverlayStatusBarItemsProvider,
-                mDreamOverlayStateController);
+                mDreamOverlayStateController,
+                mUserTracker);
     }
 
     @Test
@@ -282,7 +288,8 @@
                 mZenModeController,
                 mStatusBarWindowStateController,
                 mDreamOverlayStatusBarItemsProvider,
-                mDreamOverlayStateController);
+                mDreamOverlayStateController,
+                mUserTracker);
         controller.onViewAttached();
         verify(mView, never()).showIcon(
                 eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index 89c7280..a4cf15c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -31,13 +31,13 @@
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
-import android.widget.ImageView;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.view.LaunchableImageView;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.controller.ControlsController;
 import com.android.systemui.controls.controller.StructureInfo;
@@ -90,7 +90,7 @@
     private View mView;
 
     @Mock
-    private ImageView mHomeControlsView;
+    private LaunchableImageView mHomeControlsView;
 
     @Mock
     private ActivityStarter mActivityStarter;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
new file mode 100644
index 0000000..19347c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.systemui.dreams.conditions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.condition.Condition;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamConditionTest extends SysuiTestCase {
+    @Mock
+    Context mContext;
+
+    @Mock
+    Condition.Callback mCallback;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Ensure a dreaming state immediately triggers the condition.
+     */
+    @Test
+    public void testInitialState() {
+        final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
+        when(mContext.registerReceiver(any(), any())).thenReturn(intent);
+        final DreamCondition condition = new DreamCondition(mContext);
+        condition.addCallback(mCallback);
+        condition.start();
+
+        verify(mCallback).onConditionChanged(eq(condition));
+        assertThat(condition.isConditionMet()).isTrue();
+    }
+
+    /**
+     * Ensure that changing dream state triggers condition.
+     */
+    @Test
+    public void testChange() {
+        final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED);
+        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        when(mContext.registerReceiver(receiverCaptor.capture(), any())).thenReturn(intent);
+        final DreamCondition condition = new DreamCondition(mContext);
+        condition.addCallback(mCallback);
+        condition.start();
+        clearInvocations(mCallback);
+        receiverCaptor.getValue().onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED));
+        verify(mCallback).onConditionChanged(eq(condition));
+        assertThat(condition.isConditionMet()).isFalse();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index f64179d..3a168d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -41,12 +41,13 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.touch.scrim.ScrimController;
+import com.android.systemui.dreams.touch.scrim.ScrimManager;
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
 import org.junit.Before;
@@ -63,10 +64,13 @@
 @RunWith(AndroidTestingRunner.class)
 public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
     @Mock
-    StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    CentralSurfaces mCentralSurfaces;
 
     @Mock
-    CentralSurfaces mCentralSurfaces;
+    ScrimManager mScrimManager;
+
+    @Mock
+    ScrimController mScrimController;
 
     @Mock
     NotificationShadeWindowController mNotificationShadeWindowController;
@@ -111,7 +115,7 @@
         MockitoAnnotations.initMocks(this);
         mTouchHandler = new BouncerSwipeTouchHandler(
                 mDisplayMetrics,
-                mStatusBarKeyguardViewManager,
+                mScrimManager,
                 Optional.of(mCentralSurfaces),
                 mNotificationShadeWindowController,
                 mValueAnimatorCreator,
@@ -121,6 +125,7 @@
                 TOUCH_REGION,
                 mUiEventLogger);
 
+        when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
         when(mCentralSurfaces.isBouncerShowing()).thenReturn(false);
         when(mCentralSurfaces.getDisplayHeight()).thenReturn((float) SCREEN_HEIGHT_PX);
         when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
@@ -193,7 +198,7 @@
         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
                 .isTrue();
 
-        verify(mStatusBarKeyguardViewManager, never()).onPanelExpansionChanged(any());
+        verify(mScrimController, never()).expand(any());
     }
 
     /**
@@ -220,7 +225,7 @@
         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
                 .isTrue();
 
-        verify(mStatusBarKeyguardViewManager, never()).onPanelExpansionChanged(any());
+        verify(mScrimController, never()).expand(any());
     }
 
     /**
@@ -274,12 +279,12 @@
         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
                 0, direction == Direction.UP ? SCREEN_HEIGHT_PX - distanceY : distanceY, 0);
 
-        reset(mStatusBarKeyguardViewManager);
+        reset(mScrimController);
         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
                 .isTrue();
 
         // Ensure only called once
-        verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(any());
+        verify(mScrimController).expand(any());
 
         final float expansion = isBouncerInitiallyShowing ? percent : 1 - percent;
         final float dragDownAmount = event2.getY() - event1.getY();
@@ -288,7 +293,7 @@
         ShadeExpansionChangeEvent event =
                 new ShadeExpansionChangeEvent(
                         expansion, /* expanded= */ false, /* tracking= */ true, dragDownAmount);
-        verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(event);
+        verify(mScrimController).expand(event);
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index a807407..178b9cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -424,6 +424,32 @@
         verify(gestureListener2).onDown(eq(followupEvent));
     }
 
+    @Test
+    public void testOnRemovedCallbackOnStopMonitoring() {
+        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler.TouchSession.Callback callback =
+                Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)));
+
+        final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+        environment.publishInputEvent(initialEvent);
+
+        final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+        session.registerCallback(callback);
+
+        environment.executeAll();
+
+        environment.updateLifecycle(observerOwnerPair -> {
+            observerOwnerPair.first.onPause(observerOwnerPair.second);
+        });
+
+        environment.executeAll();
+
+        verify(callback).onRemoved();
+    }
+
     public GestureDetector.OnGestureListener registerGestureListener(DreamTouchHandler handler) {
         final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
                 GestureDetector.OnGestureListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
new file mode 100644
index 0000000..79c535a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.systemui.dreams.touch.scrim;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.os.PowerManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class BouncerlessScrimControllerTest extends SysuiTestCase {
+    @Mock
+    BouncerlessScrimController.Callback mCallback;
+
+    @Mock
+    PowerManager mPowerManager;
+
+    private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testWakeupOnSwipeOpen() {
+        final BouncerlessScrimController scrimController =
+                new BouncerlessScrimController(mExecutor, mPowerManager);
+        scrimController.addCallback(mCallback);
+        scrimController.expand(new ShadeExpansionChangeEvent(.5f, true, false, 0.0f));
+        mExecutor.runAllReady();
+        verify(mPowerManager).wakeUp(anyLong(), eq(PowerManager.WAKE_REASON_GESTURE), any());
+        verify(mCallback).onWakeup();
+    }
+
+    @Test
+    public void testExpansionPropagation() {
+        final BouncerlessScrimController scrimController =
+                new BouncerlessScrimController(mExecutor, mPowerManager);
+        scrimController.addCallback(mCallback);
+        final ShadeExpansionChangeEvent expansionEvent =
+                new ShadeExpansionChangeEvent(0.5f, false, false, 0.0f);
+        scrimController.expand(expansionEvent);
+        mExecutor.runAllReady();
+        verify(mCallback).onExpansion(eq(expansionEvent));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
new file mode 100644
index 0000000..ac9822d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.systemui.dreams.touch.scrim;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ScrimManagerTest extends SysuiTestCase {
+    @Mock
+    ScrimController mBouncerlessScrimController;
+
+    @Mock
+    ScrimController mBouncerScrimController;
+
+    @Mock
+    KeyguardStateController mKeyguardStateController;
+
+    @Mock
+    ScrimManager.Callback mCallback;
+
+    private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testControllerSelection() {
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+        ArgumentCaptor<KeyguardStateController.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+        final ScrimManager manager = new ScrimManager(mExecutor, mBouncerScrimController,
+                mBouncerlessScrimController, mKeyguardStateController);
+        verify(mKeyguardStateController).addCallback(callbackCaptor.capture());
+
+        assertThat(manager.getCurrentController()).isEqualTo(mBouncerScrimController);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+        callbackCaptor.getValue().onKeyguardShowingChanged();
+        mExecutor.runAllReady();
+        assertThat(manager.getCurrentController()).isEqualTo(mBouncerlessScrimController);
+    }
+
+    @Test
+    public void testCallback() {
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+        ArgumentCaptor<KeyguardStateController.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+        final ScrimManager manager = new ScrimManager(mExecutor, mBouncerScrimController,
+                mBouncerlessScrimController, mKeyguardStateController);
+        verify(mKeyguardStateController).addCallback(callbackCaptor.capture());
+
+        manager.addCallback(mCallback);
+        when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+        callbackCaptor.getValue().onKeyguardShowingChanged();
+        mExecutor.runAllReady();
+        verify(mCallback).onScrimControllerChanged(eq(mBouncerlessScrimController));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 170a70f..35f0f6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -125,7 +125,7 @@
         flags.set(unreleasedFlag, false)
         flags.set(unreleasedFlag, false)
 
-        listener.verifyInOrder(unreleasedFlag.id, unreleasedFlag.id)
+        listener.verifyInOrder(unreleasedFlag.name, unreleasedFlag.name)
     }
 
     @Test
@@ -137,7 +137,7 @@
         flags.set(stringFlag, "Test")
         flags.set(stringFlag, "Test")
 
-        listener.verifyInOrder(stringFlag.id)
+        listener.verifyInOrder(stringFlag.name)
     }
 
     @Test
@@ -149,7 +149,7 @@
         flags.removeListener(listener)
         flags.set(unreleasedFlag, false)
 
-        listener.verifyInOrder(unreleasedFlag.id)
+        listener.verifyInOrder(unreleasedFlag.name)
     }
 
     @Test
@@ -162,7 +162,7 @@
         flags.removeListener(listener)
         flags.set(stringFlag, "Other")
 
-        listener.verifyInOrder(stringFlag.id)
+        listener.verifyInOrder(stringFlag.name)
     }
 
     @Test
@@ -175,7 +175,7 @@
         flags.set(releasedFlag, true)
         flags.set(unreleasedFlag, true)
 
-        listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+        listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name)
     }
 
     @Test
@@ -191,7 +191,7 @@
         flags.set(releasedFlag, false)
         flags.set(unreleasedFlag, false)
 
-        listener.verifyInOrder(releasedFlag.id, unreleasedFlag.id)
+        listener.verifyInOrder(releasedFlag.name, unreleasedFlag.name)
     }
 
     @Test
@@ -204,8 +204,8 @@
 
         flags.set(releasedFlag, true)
 
-        listener1.verifyInOrder(releasedFlag.id)
-        listener2.verifyInOrder(releasedFlag.id)
+        listener1.verifyInOrder(releasedFlag.name)
+        listener2.verifyInOrder(releasedFlag.name)
     }
 
     @Test
@@ -220,18 +220,18 @@
         flags.removeListener(listener2)
         flags.set(releasedFlag, false)
 
-        listener1.verifyInOrder(releasedFlag.id, releasedFlag.id)
-        listener2.verifyInOrder(releasedFlag.id)
+        listener1.verifyInOrder(releasedFlag.name, releasedFlag.name)
+        listener2.verifyInOrder(releasedFlag.name)
     }
 
     class VerifyingListener : FlagListenable.Listener {
-        var flagEventIds = mutableListOf<Int>()
+        var flagEventNames = mutableListOf<String>()
         override fun onFlagChanged(event: FlagListenable.FlagEvent) {
-            flagEventIds.add(event.flagId)
+            flagEventNames.add(event.flagName)
         }
 
-        fun verifyInOrder(vararg eventIds: Int) {
-            assertThat(flagEventIds).containsExactlyElementsIn(eventIds.asList())
+        fun verifyInOrder(vararg eventNames: String) {
+            assertThat(flagEventNames).containsExactlyElementsIn(eventNames.asList())
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 7592cc5..d8bbd04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -23,12 +23,11 @@
 import android.content.res.Resources.NotFoundException
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import com.android.systemui.util.DeviceConfigProxyFake
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert
@@ -62,21 +61,20 @@
     @Mock
     private lateinit var mockContext: Context
     @Mock
+    private lateinit var globalSettings: GlobalSettings
+    @Mock
     private lateinit var secureSettings: SecureSettings
     @Mock
     private lateinit var systemProperties: SystemPropertiesHelper
     @Mock
     private lateinit var resources: Resources
     @Mock
-    private lateinit var commandRegistry: CommandRegistry
-    @Mock
     private lateinit var restarter: Restarter
-    private val flagMap = mutableMapOf<Int, Flag<*>>()
+    private val flagMap = mutableMapOf<String, Flag<*>>()
     private lateinit var broadcastReceiver: BroadcastReceiver
-    private lateinit var clearCacheAction: Consumer<Int>
+    private lateinit var clearCacheAction: Consumer<String>
     private val serverFlagReader = ServerFlagReaderFake()
 
-    private val deviceConfig = DeviceConfigProxyFake()
     private val teamfoodableFlagA = UnreleasedFlag(
         500, name = "a", namespace = "test", teamfood = true
     )
@@ -87,11 +85,13 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        flagMap.put(teamfoodableFlagA.id, teamfoodableFlagA)
-        flagMap.put(teamfoodableFlagB.id, teamfoodableFlagB)
+        flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD)
+        flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
+        flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)
         mFeatureFlagsDebug = FeatureFlagsDebug(
             flagManager,
             mockContext,
+            globalSettings,
             secureSettings,
             systemProperties,
             resources,
@@ -110,14 +110,14 @@
         clearCacheAction = withArgCaptor {
             verify(flagManager).clearCacheAction = capture()
         }
-        whenever(flagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
+        whenever(flagManager.nameToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
     }
 
     @Test
     fun readBooleanFlag() {
         // Remember that the TEAMFOOD flag is id#1 and has special behavior.
-        whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
-        whenever(flagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false)
+        whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
+        whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false)
 
         assertThat(
             mFeatureFlagsDebug.isEnabled(
@@ -141,7 +141,7 @@
             mFeatureFlagsDebug.isEnabled(
                 ReleasedFlag(
                     4,
-                    name = "3",
+                    name = "4",
                     namespace = "test"
                 )
             )
@@ -150,7 +150,7 @@
             mFeatureFlagsDebug.isEnabled(
                 UnreleasedFlag(
                     5,
-                    name = "4",
+                    name = "5",
                     namespace = "test"
                 )
             )
@@ -159,7 +159,8 @@
 
     @Test
     fun teamFoodFlag_False() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(false)
+        whenever(flagManager.readFlagValue<Boolean>(
+            eq(Flags.TEAMFOOD.name), any())).thenReturn(false)
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isFalse()
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -170,7 +171,8 @@
 
     @Test
     fun teamFoodFlag_True() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
+        whenever(flagManager.readFlagValue<Boolean>(
+            eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isTrue()
 
@@ -181,11 +183,12 @@
 
     @Test
     fun teamFoodFlag_Overridden() {
-        whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.id), any()))
+        whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.name), any()))
             .thenReturn(true)
-        whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.id), any()))
+        whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))
             .thenReturn(false)
-        whenever(flagManager.readFlagValue<Boolean>(eq(1), any())).thenReturn(true)
+        whenever(flagManager.readFlagValue<Boolean>(
+            eq(Flags.TEAMFOOD.name), any())).thenReturn(true)
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagA)).isTrue()
         assertThat(mFeatureFlagsDebug.isEnabled(teamfoodableFlagB)).isFalse()
 
@@ -202,8 +205,8 @@
         whenever(resources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() }
         whenever(resources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() }
 
-        whenever(flagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
-        whenever(flagManager.readFlagValue<Boolean>(eq(5), any())).thenReturn(false)
+        whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true)
+        whenever(flagManager.readFlagValue<Boolean>(eq("5"), any())).thenReturn(false)
 
         assertThat(
             mFeatureFlagsDebug.isEnabled(
@@ -255,8 +258,8 @@
 
     @Test
     fun readStringFlag() {
-        whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
-        whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar")
+        whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo")
+        whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar")
         assertThat(mFeatureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
         assertThat(mFeatureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
         assertThat(mFeatureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
@@ -272,9 +275,9 @@
         whenever(resources.getString(1005)).thenAnswer { throw NameNotFoundException() }
         whenever(resources.getString(1006)).thenAnswer { throw NameNotFoundException() }
 
-        whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3")
-        whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4")
-        whenever(flagManager.readFlagValue<String>(eq(6), any())).thenReturn("override6")
+        whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("override3")
+        whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("override4")
+        whenever(flagManager.readFlagValue<String>(eq("6"), any())).thenReturn("override6")
 
         assertThat(
             mFeatureFlagsDebug.getString(
@@ -322,8 +325,8 @@
 
     @Test
     fun readIntFlag() {
-        whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(22)
-        whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(48)
+        whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22)
+        whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48)
         assertThat(mFeatureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
         assertThat(mFeatureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
         assertThat(mFeatureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
@@ -368,12 +371,12 @@
         broadcastReceiver.onReceive(mockContext, Intent())
         broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
         broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
-        setByBroadcast(0, false) // unknown id does nothing
-        setByBroadcast(1, "string") // wrong type does nothing
-        setByBroadcast(2, 123) // wrong type does nothing
-        setByBroadcast(3, false) // wrong type does nothing
-        setByBroadcast(4, 123) // wrong type does nothing
-        verifyNoMoreInteractions(flagManager, secureSettings)
+        setByBroadcast("0", false) // unknown id does nothing
+        setByBroadcast("1", "string") // wrong type does nothing
+        setByBroadcast("2", 123) // wrong type does nothing
+        setByBroadcast("3", false) // wrong type does nothing
+        setByBroadcast("4", 123) // wrong type does nothing
+        verifyNoMoreInteractions(flagManager, globalSettings)
     }
 
     @Test
@@ -383,16 +386,16 @@
         // trying to erase an id not in the map does nothing
         broadcastReceiver.onReceive(
             mockContext,
-            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 0)
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "")
         )
-        verifyNoMoreInteractions(flagManager, secureSettings)
+        verifyNoMoreInteractions(flagManager, globalSettings)
 
         // valid id with no value puts empty string in the setting
         broadcastReceiver.onReceive(
             mockContext,
-            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 1)
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1")
         )
-        verifyPutData(1, "", numReads = 0)
+        verifyPutData("1", "", numReads = 0)
     }
 
     @Test
@@ -402,51 +405,51 @@
         addFlag(ResourceBooleanFlag(3, "3", "test", 1003))
         addFlag(ResourceBooleanFlag(4, "4", "test", 1004))
 
-        setByBroadcast(1, false)
-        verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}")
+        setByBroadcast("1", false)
+        verifyPutData("1", "{\"type\":\"boolean\",\"value\":false}")
 
-        setByBroadcast(2, true)
-        verifyPutData(2, "{\"type\":\"boolean\",\"value\":true}")
+        setByBroadcast("2", true)
+        verifyPutData("2", "{\"type\":\"boolean\",\"value\":true}")
 
-        setByBroadcast(3, false)
-        verifyPutData(3, "{\"type\":\"boolean\",\"value\":false}")
+        setByBroadcast("3", false)
+        verifyPutData("3", "{\"type\":\"boolean\",\"value\":false}")
 
-        setByBroadcast(4, true)
-        verifyPutData(4, "{\"type\":\"boolean\",\"value\":true}")
+        setByBroadcast("4", true)
+        verifyPutData("4", "{\"type\":\"boolean\",\"value\":true}")
     }
 
     @Test
     fun setStringFlag() {
-        addFlag(StringFlag(1, "flag1", "1", "test"))
+        addFlag(StringFlag(1, "1", "1", "test"))
         addFlag(ResourceStringFlag(2, "2", "test", 1002))
 
-        setByBroadcast(1, "override1")
-        verifyPutData(1, "{\"type\":\"string\",\"value\":\"override1\"}")
+        setByBroadcast("1", "override1")
+        verifyPutData("1", "{\"type\":\"string\",\"value\":\"override1\"}")
 
-        setByBroadcast(2, "override2")
-        verifyPutData(2, "{\"type\":\"string\",\"value\":\"override2\"}")
+        setByBroadcast("2", "override2")
+        verifyPutData("2", "{\"type\":\"string\",\"value\":\"override2\"}")
     }
 
     @Test
     fun setFlag_ClearsCache() {
         val flag1 = addFlag(StringFlag(1, "1", "test", "flag1"))
-        whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("original")
+        whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original")
 
         // gets the flag & cache it
         assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
-        verify(flagManager).readFlagValue(eq(1), eq(StringFlagSerializer))
+        verify(flagManager, times(1)).readFlagValue(eq("1"), eq(StringFlagSerializer))
 
         // hit the cache
         assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
         verifyNoMoreInteractions(flagManager)
 
         // set the flag
-        setByBroadcast(1, "new")
-        verifyPutData(1, "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
-        whenever(flagManager.readFlagValue<String>(eq(1), any())).thenReturn("new")
+        setByBroadcast("1", "new")
+        verifyPutData("1", "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
+        whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("new")
 
         assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new")
-        verify(flagManager, times(3)).readFlagValue(eq(1), eq(StringFlagSerializer))
+        verify(flagManager, times(3)).readFlagValue(eq("1"), eq(StringFlagSerializer))
     }
 
     @Test
@@ -463,7 +466,6 @@
         val flag = UnreleasedFlag(100, name = "100", namespace = "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
-
         assertThat(mFeatureFlagsDebug.isEnabled(flag)).isTrue()
     }
 
@@ -503,26 +505,26 @@
         assertThat(dump).contains(" sysui_flag_7: [length=9] \"override7\"\n")
     }
 
-    private fun verifyPutData(id: Int, data: String, numReads: Int = 1) {
-        inOrder(flagManager, secureSettings).apply {
-            verify(flagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>())
-            verify(flagManager).idToSettingsKey(eq(id))
-            verify(secureSettings).putStringForUser(eq("key-$id"), eq(data), anyInt())
-            verify(flagManager).dispatchListenersAndMaybeRestart(eq(id), any())
+    private fun verifyPutData(name: String, data: String, numReads: Int = 1) {
+        inOrder(flagManager, globalSettings).apply {
+            verify(flagManager, times(numReads)).readFlagValue(eq(name), any<FlagSerializer<*>>())
+            verify(flagManager).nameToSettingsKey(eq(name))
+            verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt())
+            verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any())
         }.verifyNoMoreInteractions()
-        verifyNoMoreInteractions(flagManager, secureSettings)
+        verifyNoMoreInteractions(flagManager, globalSettings)
     }
 
-    private fun setByBroadcast(id: Int, value: Serializable?) {
+    private fun setByBroadcast(name: String, value: Serializable?) {
         val intent = Intent(FlagManager.ACTION_SET_FLAG)
-        intent.putExtra(FlagManager.EXTRA_ID, id)
+        intent.putExtra(FlagManager.EXTRA_NAME, name)
         intent.putExtra(FlagManager.EXTRA_VALUE, value)
         broadcastReceiver.onReceive(mockContext, intent)
     }
 
     private fun <F : Flag<*>> addFlag(flag: F): F {
-        val old = flagMap.put(flag.id, flag)
-        check(old == null) { "Flag ${flag.id} already registered" }
+        val old = flagMap.put(flag.name, flag)
+        check(old == null) { "Flag ${flag.name} already registered" }
         return flag
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index d5b5a4a..4c6028c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -19,7 +19,6 @@
 import android.content.res.Resources
 import android.test.suitebuilder.annotation.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.DeviceConfigProxyFake
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
 import org.junit.Before
@@ -39,9 +38,8 @@
     @Mock private lateinit var mResources: Resources
     @Mock private lateinit var mSystemProperties: SystemPropertiesHelper
     @Mock private lateinit var restarter: Restarter
-    private val flagMap = mutableMapOf<Int, Flag<*>>()
+    private val flagMap = mutableMapOf<String, Flag<*>>()
     private val serverFlagReader = ServerFlagReaderFake()
-    private val deviceConfig = DeviceConfigProxyFake()
 
     @Before
     fun setup() {
@@ -49,7 +47,6 @@
         mFeatureFlagsRelease = FeatureFlagsRelease(
             mResources,
             mSystemProperties,
-            deviceConfig,
             serverFlagReader,
             flagMap,
             restarter)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index fea91c5..28131b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -32,7 +32,7 @@
 
     @Mock private lateinit var featureFlags: FeatureFlagsDebug
     @Mock private lateinit var pw: PrintWriter
-    private val flagMap = mutableMapOf<Int, Flag<*>>()
+    private val flagMap = mutableMapOf<String, Flag<*>>()
     private val flagA = UnreleasedFlag(500, "500", "test")
     private val flagB = ReleasedFlag(501, "501", "test")
     private val stringFlag = StringFlag(502, "502", "test", "abracadabra")
@@ -53,59 +53,59 @@
             (invocation.getArgument(0) as IntFlag).default
         }
 
-        flagMap.put(flagA.id, flagA)
-        flagMap.put(flagB.id, flagB)
-        flagMap.put(stringFlag.id, stringFlag)
-        flagMap.put(intFlag.id, intFlag)
+        flagMap.put(flagA.name, flagA)
+        flagMap.put(flagB.name, flagB)
+        flagMap.put(stringFlag.name, stringFlag)
+        flagMap.put(intFlag.name, intFlag)
 
         cmd = FlagCommand(featureFlags, flagMap)
     }
 
     @Test
     fun readBooleanFlagCommand() {
-        cmd.execute(pw, listOf(flagA.id.toString()))
+        cmd.execute(pw, listOf(flagA.name))
         Mockito.verify(featureFlags).isEnabled(flagA)
     }
 
     @Test
     fun readStringFlagCommand() {
-        cmd.execute(pw, listOf(stringFlag.id.toString()))
+        cmd.execute(pw, listOf(stringFlag.name))
         Mockito.verify(featureFlags).getString(stringFlag)
     }
 
     @Test
     fun readIntFlag() {
-        cmd.execute(pw, listOf(intFlag.id.toString()))
+        cmd.execute(pw, listOf(intFlag.name))
         Mockito.verify(featureFlags).getInt(intFlag)
     }
 
     @Test
     fun setBooleanFlagCommand() {
-        cmd.execute(pw, listOf(flagB.id.toString(), "on"))
+        cmd.execute(pw, listOf(flagB.name, "on"))
         Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, true)
     }
 
     @Test
     fun setStringFlagCommand() {
-        cmd.execute(pw, listOf(stringFlag.id.toString(), "set", "foobar"))
+        cmd.execute(pw, listOf(stringFlag.name, "set", "foobar"))
         Mockito.verify(featureFlags).setStringFlagInternal(stringFlag, "foobar")
     }
 
     @Test
     fun setIntFlag() {
-        cmd.execute(pw, listOf(intFlag.id.toString(), "put", "123"))
+        cmd.execute(pw, listOf(intFlag.name, "put", "123"))
         Mockito.verify(featureFlags).setIntFlagInternal(intFlag, 123)
     }
 
     @Test
     fun toggleBooleanFlagCommand() {
-        cmd.execute(pw, listOf(flagB.id.toString(), "toggle"))
+        cmd.execute(pw, listOf(flagB.name, "toggle"))
         Mockito.verify(featureFlags).setBooleanFlagInternal(flagB, false)
     }
 
     @Test
     fun eraseFlagCommand() {
-        cmd.execute(pw, listOf(flagA.id.toString(), "erase"))
+        cmd.execute(pw, listOf(flagA.name, "erase"))
         Mockito.verify(featureFlags).eraseFlag(flagA)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index fca7e96..e679d47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -87,14 +87,14 @@
     @Test
     fun testObserverClearsCache() {
         val listener = mock<FlagListenable.Listener>()
-        val clearCacheAction = mock<Consumer<Int>>()
+        val clearCacheAction = mock<Consumer<String>>()
         mFlagManager.clearCacheAction = clearCacheAction
         mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
         observer.onChange(false, flagUri(1))
-        verify(clearCacheAction).accept(eq(1))
+        verify(clearCacheAction).accept(eq("1"))
     }
 
     @Test
@@ -110,14 +110,14 @@
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener1).onFlagChanged(capture())
         }
-        assertThat(flagEvent1.flagId).isEqualTo(1)
+        assertThat(flagEvent1.flagName).isEqualTo("1")
         verifyNoMoreInteractions(listener1, listener10)
 
         observer.onChange(false, flagUri(10))
         val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener10).onFlagChanged(capture())
         }
-        assertThat(flagEvent10.flagId).isEqualTo(10)
+        assertThat(flagEvent10.flagName).isEqualTo("10")
         verifyNoMoreInteractions(listener1, listener10)
     }
 
@@ -130,18 +130,18 @@
         mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
         mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(1, null)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener1).onFlagChanged(capture())
         }
-        assertThat(flagEvent1.flagId).isEqualTo(1)
+        assertThat(flagEvent1.flagName).isEqualTo("1")
         verifyNoMoreInteractions(listener1, listener10)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(10, null)
+        mFlagManager.dispatchListenersAndMaybeRestart("10", null)
         val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener10).onFlagChanged(capture())
         }
-        assertThat(flagEvent10.flagId).isEqualTo(10)
+        assertThat(flagEvent10.flagName).isEqualTo("10")
         verifyNoMoreInteractions(listener1, listener10)
     }
 
@@ -151,25 +151,25 @@
         mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
         mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(1, null)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener).onFlagChanged(capture())
         }
-        assertThat(flagEvent1.flagId).isEqualTo(1)
+        assertThat(flagEvent1.flagName).isEqualTo("1")
         verifyNoMoreInteractions(listener)
 
-        mFlagManager.dispatchListenersAndMaybeRestart(10, null)
+        mFlagManager.dispatchListenersAndMaybeRestart("10", null)
         val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
             verify(listener, times(2)).onFlagChanged(capture())
         }
-        assertThat(flagEvent10.flagId).isEqualTo(10)
+        assertThat(flagEvent10.flagName).isEqualTo("10")
         verifyNoMoreInteractions(listener)
     }
 
     @Test
     fun testRestartWithNoListeners() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
         verify(restartAction).accept(eq(false))
         verifyNoMoreInteractions(restartAction)
     }
@@ -180,7 +180,7 @@
         mFlagManager.addListener(ReleasedFlag(1, "1", "test")) { event ->
             event.requestNoRestart()
         }
-        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
         verify(restartAction).accept(eq(true))
         verifyNoMoreInteractions(restartAction)
     }
@@ -191,7 +191,7 @@
         mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
             event.requestNoRestart()
         }
-        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
         verify(restartAction).accept(eq(false))
         verifyNoMoreInteractions(restartAction)
     }
@@ -205,7 +205,7 @@
         mFlagManager.addListener(ReleasedFlag(10, "10", "test")) {
             // do not request
         }
-        mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
+        mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
         verify(restartAction).accept(eq(false))
         verifyNoMoreInteractions(restartAction)
     }
@@ -214,31 +214,31 @@
     fun testReadBooleanFlag() {
         // test that null string returns null
         whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull()
 
         // test that empty string returns null
         whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull()
 
         // test false
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"boolean\",\"value\":false}")
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isFalse()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isFalse()
 
         // test true
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"boolean\",\"value\":true}")
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isTrue()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isTrue()
 
         // Reading a value of a different type should just return null
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
-        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", BooleanFlagSerializer)).isNull()
 
         // Reading a value that isn't json should throw an exception
         assertThrows(InvalidFlagStorageException::class.java) {
             whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
-            mFlagManager.readFlagValue(1, BooleanFlagSerializer)
+            mFlagManager.readFlagValue("1", BooleanFlagSerializer)
         }
     }
 
@@ -257,31 +257,31 @@
     fun testReadStringFlag() {
         // test that null string returns null
         whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull()
 
         // test that empty string returns null
         whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull()
 
         // test json with the empty string value returns empty string
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"string\",\"value\":\"\"}")
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("")
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isEqualTo("")
 
         // test string with value is returned
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("foo")
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isEqualTo("foo")
 
         // Reading a value of a different type should just return null
         whenever(mFlagSettingsHelper.getString(any()))
             .thenReturn("{\"type\":\"boolean\",\"value\":false}")
-        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+        assertThat(mFlagManager.readFlagValue("1", StringFlagSerializer)).isNull()
 
         // Reading a value that isn't json should throw an exception
         assertThrows(InvalidFlagStorageException::class.java) {
             whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
-            mFlagManager.readFlagValue(1, StringFlagSerializer)
+            mFlagManager.readFlagValue("1", StringFlagSerializer)
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
index 77c837b..a2dc1eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
@@ -14,6 +14,7 @@
 @SmallTest
 class FragmentServiceTest : SysuiTestCase() {
     private val fragmentCreator = TestFragmentCreator()
+    private val fragmenetHostManagerFactory: FragmentHostManager.Factory = mock()
     private val fragmentCreatorFactory = FragmentService.FragmentCreator.Factory { fragmentCreator }
 
     private lateinit var fragmentService: FragmentService
@@ -24,7 +25,13 @@
             Looper.prepare()
         }
 
-        fragmentService = FragmentService(fragmentCreatorFactory, mock(), DumpManager())
+        fragmentService =
+            FragmentService(
+                fragmentCreatorFactory,
+                fragmenetHostManagerFactory,
+                mock(),
+                DumpManager()
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 0a03b2c..fb54d6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -62,6 +62,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -173,6 +174,7 @@
                         set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
                         set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
                         set(Flags.REVAMPED_WALLPAPER_UI, true)
+                        set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
                     },
                 repository = { quickAffordanceRepository },
                 launchAnimator = launchAnimator,
@@ -183,6 +185,7 @@
                 mainDispatcher = testDispatcher,
                 backgroundHandler = backgroundHandler,
             )
+        underTest.mainDispatcher = UnconfinedTestDispatcher()
 
         underTest.attachInfoForTesting(
             context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index e9db8cc..b9cfc65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -35,12 +36,12 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.UserHandle;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
@@ -71,6 +72,7 @@
     private @Mock Context mContext;
     private @Mock TaskStackChangeListeners mTaskStackChangeListeners;
     private @Mock IActivityTaskManager mIActivityTaskManager;
+    private @Mock UserTracker mUserTracker;
 
     private WorkLockActivityController mController;
     private TaskStackChangeListener mTaskStackListener;
@@ -81,12 +83,13 @@
 
         // Set a package name to use for checking ComponentName well-formedness in tests.
         doReturn("com.example.test").when(mContext).getPackageName();
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
 
         // Construct controller. Save the TaskStackListener for injecting events.
         final ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
                 ArgumentCaptor.forClass(TaskStackChangeListener.class);
-        mController = new WorkLockActivityController(mContext, mTaskStackChangeListeners,
-                mIActivityTaskManager);
+        mController = new WorkLockActivityController(mContext, mUserTracker,
+                mTaskStackChangeListeners, mIActivityTaskManager);
 
         verify(mTaskStackChangeListeners).registerTaskStackListener(listenerCaptor.capture());
         mTaskStackListener = listenerCaptor.getValue();
@@ -135,7 +138,7 @@
                 anyInt(),
                 eq((ProfilerInfo) null),
                 argThat(hasOptions(taskId, taskOverlay)),
-                eq(UserHandle.USER_CURRENT));
+                eq(ActivityManager.getCurrentUser()));
     }
 
     private void verifyStartActivity(int taskId, boolean taskOverlay) throws Exception {
@@ -151,7 +154,7 @@
                 anyInt(),
                 eq((ProfilerInfo) null),
                 argThat(hasOptions(taskId, taskOverlay)),
-                eq(UserHandle.USER_CURRENT));
+                eq(ActivityManager.getCurrentUser()));
     }
 
     private static ArgumentMatcher<Intent> hasComponent(final Context context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 7205f30..58cdec4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -19,9 +19,14 @@
 
 import android.app.StatusBarManager
 import android.content.Context
+import android.content.pm.PackageManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -31,22 +36,26 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class CameraQuickAffordanceConfigTest : SysuiTestCase() {
 
     @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
     @Mock private lateinit var context: Context
+    @Mock private lateinit var packageManager: PackageManager
 
     private lateinit var underTest: CameraQuickAffordanceConfig
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        setLaunchable(true)
 
         underTest =
             CameraQuickAffordanceConfig(
                 context,
+                packageManager,
             ) {
                 cameraGestureHelper
             }
@@ -62,4 +71,25 @@
             .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
         assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
     }
+
+    @Test
+    fun `getPickerScreenState - default when launchable`() = runTest {
+        setLaunchable(true)
+
+        Truth.assertThat(underTest.getPickerScreenState())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+    }
+
+    @Test
+    fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+        setLaunchable(false)
+
+        Truth.assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+    }
+
+    private fun setLaunchable(isLaunchable: Boolean) {
+        whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY))
+            .thenReturn(isLaunchable)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..925c06f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 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.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MuteQuickAffordanceConfigTest : SysuiTestCase() {
+
+    private lateinit var underTest: MuteQuickAffordanceConfig
+    @Mock
+    private lateinit var ringerModeTracker: RingerModeTracker
+    @Mock
+    private lateinit var audioManager: AudioManager
+    @Mock
+    private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var userFileManager: UserFileManager
+
+    private lateinit var testDispatcher: TestDispatcher
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+
+        whenever(userTracker.userContext).thenReturn(context)
+        whenever(userFileManager.getSharedPreferences(any(), any(), any()))
+                .thenReturn(context.getSharedPreferences("mutequickaffordancetest", Context.MODE_PRIVATE))
+
+        underTest = MuteQuickAffordanceConfig(
+                context,
+                userTracker,
+                userFileManager,
+                ringerModeTracker,
+                audioManager,
+                testScope.backgroundScope,
+                testDispatcher,
+                testDispatcher,
+        )
+    }
+
+    @Test
+    fun `picker state - volume fixed - not available`() = testScope.runTest {
+        //given
+        whenever(audioManager.isVolumeFixed).thenReturn(true)
+
+        //when
+        val result = underTest.getPickerScreenState()
+
+        //then
+        assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice, result)
+    }
+
+    @Test
+    fun `picker state - volume not fixed - available`() = testScope.runTest {
+        //given
+        whenever(audioManager.isVolumeFixed).thenReturn(false)
+
+        //when
+        val result = underTest.getPickerScreenState()
+
+        //then
+        assertEquals(KeyguardQuickAffordanceConfig.PickerScreenState.Default(), result)
+    }
+
+    @Test
+    fun `triggered - state was previously NORMAL - currently SILENT - move to previous state`() = testScope.runTest {
+        //given
+        val ringerModeCapture = argumentCaptor<Int>()
+        whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+        underTest.onTriggered(null)
+        whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
+
+        //when
+        val result = underTest.onTriggered(null)
+        runCurrent()
+        verify(audioManager, times(2)).ringerModeInternal = ringerModeCapture.capture()
+
+        //then
+        assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+        assertEquals(AudioManager.RINGER_MODE_NORMAL, ringerModeCapture.value)
+    }
+
+    @Test
+    fun `triggered - state is not SILENT - move to SILENT ringer`() = testScope.runTest {
+        //given
+        val ringerModeCapture = argumentCaptor<Int>()
+        whenever(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
+
+        //when
+        val result = underTest.onTriggered(null)
+        runCurrent()
+        verify(audioManager).ringerModeInternal = ringerModeCapture.capture()
+
+        //then
+        assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+        assertEquals(AudioManager.RINGER_MODE_SILENT, ringerModeCapture.value)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
new file mode 100644
index 0000000..34f3ed8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -0,0 +1,226 @@
+/*
+ * 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.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.media.AudioManager
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.RingerModeTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MuteQuickAffordanceCoreStartableTest : SysuiTestCase()  {
+
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var userTracker: UserTracker
+    @Mock
+    private lateinit var ringerModeTracker: RingerModeTracker
+    @Mock
+    private lateinit var userFileManager: UserFileManager
+    @Mock
+    private lateinit var keyguardQuickAffordanceRepository: KeyguardQuickAffordanceRepository
+
+    private lateinit var testDispatcher: TestDispatcher
+    private lateinit var testScope: TestScope
+
+    private lateinit var underTest: MuteQuickAffordanceCoreStartable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(true)
+
+        val config: KeyguardQuickAffordanceConfig = mock()
+        whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE)
+
+        val emission = MutableStateFlow(mapOf("testQuickAffordanceKey" to listOf(config)))
+        whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
+
+        testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+
+        underTest = MuteQuickAffordanceCoreStartable(
+            featureFlags,
+            userTracker,
+            ringerModeTracker,
+            userFileManager,
+            keyguardQuickAffordanceRepository,
+            testScope.backgroundScope,
+            testDispatcher,
+        )
+    }
+
+    @Test
+    fun `feature flag is OFF - do nothing with keyguardQuickAffordanceRepository`() = testScope.runTest {
+        //given
+        whenever(featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)).thenReturn(false)
+
+        //when
+        underTest.start()
+
+        //then
+        verifyZeroInteractions(keyguardQuickAffordanceRepository)
+        coroutineContext.cancelChildren()
+    }
+
+    @Test
+    fun `feature flag is ON - call to keyguardQuickAffordanceRepository`() = testScope.runTest {
+        //given
+        val ringerModeInternal = mock<MutableLiveData<Int>>()
+        whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+        //when
+        underTest.start()
+        runCurrent()
+
+        //then
+        verify(keyguardQuickAffordanceRepository).selections
+        coroutineContext.cancelChildren()
+    }
+
+    @Test
+    fun `ringer mode is changed to SILENT - do not save to shared preferences`() = testScope.runTest {
+        //given
+        val ringerModeInternal = mock<MutableLiveData<Int>>()
+        val observerCaptor = argumentCaptor<Observer<Int>>()
+        whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+        //when
+        underTest.start()
+        runCurrent()
+        verify(ringerModeInternal).observeForever(observerCaptor.capture())
+        observerCaptor.value.onChanged(AudioManager.RINGER_MODE_SILENT)
+
+        //then
+        verifyZeroInteractions(userFileManager)
+        coroutineContext.cancelChildren()
+    }
+
+    @Test
+    fun `ringerModeInternal changes to something not SILENT - is set in sharedpreferences`() = testScope.runTest {
+        //given
+        val newRingerMode = 99
+        val observerCaptor = argumentCaptor<Observer<Int>>()
+        val ringerModeInternal = mock<MutableLiveData<Int>>()
+        val sharedPrefs = context.getSharedPreferences("quick_affordance_mute_ringer_mode_cache_test", Context.MODE_PRIVATE)
+        whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+        whenever(
+            userFileManager.getSharedPreferences(eq("quick_affordance_mute_ringer_mode_cache"), any(), any())
+        ).thenReturn(sharedPrefs)
+
+        //when
+        underTest.start()
+        runCurrent()
+        verify(ringerModeInternal).observeForever(observerCaptor.capture())
+        observerCaptor.value.onChanged(newRingerMode)
+        runCurrent()
+        val result = sharedPrefs.getInt("key_last_non_silent_ringer_mode", -1)
+
+        //then
+        assertEquals(newRingerMode, result)
+        coroutineContext.cancelChildren()
+    }
+
+    @Test
+    fun `MUTE is in selections - observe ringerModeInternal`() = testScope.runTest {
+        //given
+        val ringerModeInternal = mock<MutableLiveData<Int>>()
+        whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+        //when
+        underTest.start()
+        runCurrent()
+
+        //then
+        verify(ringerModeInternal).observeForever(any())
+        coroutineContext.cancelChildren()
+    }
+
+    @Test
+    fun `MUTE is in selections 2x - observe ringerModeInternal`() = testScope.runTest {
+        //given
+        val config: KeyguardQuickAffordanceConfig = mock()
+        whenever(config.key).thenReturn(BuiltInKeyguardQuickAffordanceKeys.MUTE)
+        val emission = MutableStateFlow(mapOf("testKey" to listOf(config), "testkey2" to listOf(config)))
+        whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
+        val ringerModeInternal = mock<MutableLiveData<Int>>()
+        whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+        //when
+        underTest.start()
+        runCurrent()
+
+        //then
+        verify(ringerModeInternal).observeForever(any())
+        coroutineContext.cancelChildren()
+    }
+
+    @Test
+    fun `MUTE is not in selections - stop observing ringerModeInternal`() = testScope.runTest {
+        //given
+        val config: KeyguardQuickAffordanceConfig = mock()
+        whenever(config.key).thenReturn("notmutequickaffordance")
+        val emission = MutableStateFlow(mapOf("testKey" to listOf(config)))
+        whenever(keyguardQuickAffordanceRepository.selections).thenReturn(emission)
+        val ringerModeInternal = mock<MutableLiveData<Int>>()
+        whenever(ringerModeTracker.ringerModeInternal).thenReturn(ringerModeInternal)
+
+        //when
+        underTest.start()
+        runCurrent()
+
+        //then
+        verify(ringerModeInternal).removeObserver(any())
+        coroutineContext.cancelChildren()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index ca44fa18..8f56b95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -114,20 +114,8 @@
     }
 
     @Test
-    fun `affordance - missing icon - model is none`() = runBlockingTest {
-        setUpState(hasWalletIcon = false)
-        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
-
-        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
-
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-
-        job.cancel()
-    }
-
-    @Test
     fun `affordance - no selected card - model is none`() = runBlockingTest {
-        setUpState(hasWalletIcon = false)
+        setUpState(hasSelectedCard = false)
         var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
         val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
@@ -165,7 +153,7 @@
     @Test
     fun `getPickerScreenState - unavailable`() = runTest {
         setUpState(
-            isWalletEnabled = false,
+            isWalletServiceAvailable = false,
         )
 
         assertThat(underTest.getPickerScreenState())
@@ -173,9 +161,9 @@
     }
 
     @Test
-    fun `getPickerScreenState - disabled when there is no icon`() = runTest {
+    fun `getPickerScreenState - disabled when the feature is not enabled`() = runTest {
         setUpState(
-            hasWalletIcon = false,
+            isWalletEnabled = false,
         )
 
         assertThat(underTest.getPickerScreenState())
@@ -194,20 +182,16 @@
 
     private fun setUpState(
         isWalletEnabled: Boolean = true,
+        isWalletServiceAvailable: Boolean = true,
         isWalletQuerySuccessful: Boolean = true,
-        hasWalletIcon: Boolean = true,
         hasSelectedCard: Boolean = true,
     ) {
         whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
 
         val walletClient: QuickAccessWalletClient = mock()
-        val icon: Drawable? =
-            if (hasWalletIcon) {
-                ICON
-            } else {
-                null
-            }
-        whenever(walletClient.tileIcon).thenReturn(icon)
+        whenever(walletClient.tileIcon).thenReturn(ICON)
+        whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
+
         whenever(walletController.walletClient).thenReturn(walletClient)
 
         whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..805dcec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.systemui.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraIntentsWrapper
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+    @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+
+    private lateinit var underTest: VideoCameraQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            VideoCameraQuickAffordanceConfig(
+                context = context,
+                cameraIntents = CameraIntentsWrapper(context),
+                activityIntentHelper = activityIntentHelper,
+                userTracker = FakeUserTracker(),
+            )
+    }
+
+    @Test
+    fun `lockScreenState - visible when launchable`() = runTest {
+        setLaunchable(true)
+
+        val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+        assertThat(lockScreenState())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+    }
+
+    @Test
+    fun `lockScreenState - hidden when not launchable`() = runTest {
+        setLaunchable(false)
+
+        val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+        assertThat(lockScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+    }
+
+    @Test
+    fun `getPickerScreenState - default when launchable`() = runTest {
+        setLaunchable(true)
+
+        assertThat(underTest.getPickerScreenState())
+            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+    }
+
+    @Test
+    fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+        setLaunchable(false)
+
+        assertThat(underTest.getPickerScreenState())
+            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+    }
+
+    private fun setLaunchable(isLaunchable: Boolean) {
+        whenever(
+                activityIntentHelper.getTargetActivityInfo(
+                    any(),
+                    anyInt(),
+                    anyBoolean(),
+                )
+            )
+            .thenReturn(
+                if (isLaunchable) {
+                    mock()
+                } else {
+                    null
+                }
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index a92dd3b..ddd1049 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -50,8 +50,8 @@
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidTestingRunner::class)
-class BiometricRepositoryTest : SysuiTestCase() {
-    private lateinit var underTest: BiometricRepository
+class BiometricSettingsRepositoryTest : SysuiTestCase() {
+    private lateinit var underTest: BiometricSettingsRepository
 
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -71,11 +71,11 @@
         userRepository = FakeUserRepository()
     }
 
-    private suspend fun createBiometricRepository() {
+    private suspend fun createBiometricSettingsRepository() {
         userRepository.setUserInfos(listOf(PRIMARY_USER))
         userRepository.setSelectedUserInfo(PRIMARY_USER)
         underTest =
-            BiometricRepositoryImpl(
+            BiometricSettingsRepositoryImpl(
                 context = context,
                 lockPatternUtils = lockPatternUtils,
                 broadcastDispatcher = fakeBroadcastDispatcher,
@@ -91,7 +91,7 @@
     @Test
     fun fingerprintEnrollmentChange() =
         testScope.runTest {
-            createBiometricRepository()
+            createBiometricSettingsRepository()
             val fingerprintEnabledByDevicePolicy = collectLastValue(underTest.isFingerprintEnrolled)
             runCurrent()
 
@@ -117,7 +117,7 @@
     @Test
     fun strongBiometricAllowedChange() =
         testScope.runTest {
-            createBiometricRepository()
+            createBiometricSettingsRepository()
             val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed)
             runCurrent()
 
@@ -140,7 +140,7 @@
     @Test
     fun fingerprintDisabledByDpmChange() =
         testScope.runTest {
-            createBiometricRepository()
+            createBiometricSettingsRepository()
             val fingerprintEnabledByDevicePolicy =
                 collectLastValue(underTest.isFingerprintEnabledByDevicePolicy)
             runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
new file mode 100644
index 0000000..9203f05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import android.hardware.biometrics.BiometricSourceType
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Captor private lateinit var callbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+
+    private lateinit var testScope: TestScope
+
+    private lateinit var underTest: DeviceEntryFingerprintAuthRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+
+        underTest =
+            DeviceEntryFingerprintAuthRepositoryImpl(
+                keyguardUpdateMonitor,
+                testScope.backgroundScope,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        verify(keyguardUpdateMonitor).removeCallback(callbackCaptor.value)
+    }
+
+    @Test
+    fun isLockedOut_whenFingerprintLockoutStateChanges_emitsNewValue() =
+        testScope.runTest {
+            val isLockedOutValue = collectLastValue(underTest.isLockedOut)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(callbackCaptor.capture())
+            val callback = callbackCaptor.value
+            whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
+
+            callback.onLockedOutStateChanged(BiometricSourceType.FACE)
+            assertThat(isLockedOutValue()).isFalse()
+
+            callback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
+            assertThat(isLockedOutValue()).isTrue()
+
+            whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
+            callback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
+            assertThat(isLockedOutValue()).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 969537d2..444a2a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -45,7 +45,7 @@
         MockitoAnnotations.initMocks(this)
         val testCoroutineScope = TestCoroutineScope()
         underTest =
-            KeyguardBouncerRepository(
+            KeyguardBouncerRepositoryImpl(
                 viewMediatorCallback,
                 systemClock,
                 testCoroutineScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index b071a02..6099f01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -275,10 +275,10 @@
         expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
     ) {
         assertThat(observed).isEqualTo(expected)
-        assertThat(underTest.getSelections())
+        assertThat(underTest.getCurrentSelections())
             .isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
         expected.forEach { (slotId, configs) ->
-            assertThat(underTest.getSelections(slotId)).isEqualTo(configs)
+            assertThat(underTest.getCurrentSelections(slotId)).isEqualTo(configs)
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
new file mode 100644
index 0000000..4b06905
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.TrustRepositoryLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class TrustRepositoryTest : SysuiTestCase() {
+    @Mock private lateinit var trustManager: TrustManager
+    @Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener>
+    private lateinit var userRepository: FakeUserRepository
+    private lateinit var testScope: TestScope
+    private val users = listOf(UserInfo(1, "user 1", 0), UserInfo(2, "user 1", 0))
+
+    private lateinit var underTest: TrustRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testScope = TestScope()
+        userRepository = FakeUserRepository()
+        userRepository.setUserInfos(users)
+
+        val logger =
+            TrustRepositoryLogger(
+                LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false)
+            )
+        underTest =
+            TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger)
+    }
+
+    @Test
+    fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+
+            val currentUserId = users[0].id
+            userRepository.setSelectedUserInfo(users[0])
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+            listener.onTrustChanged(true, false, currentUserId, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isTrue()
+
+            listener.onTrustChanged(false, false, currentUserId, 0, emptyList())
+
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_isFalse_byDefault() =
+        testScope.runTest {
+            runCurrent()
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_whenTrustChangesForDifferentUser_noop() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            userRepository.setSelectedUserInfo(users[0])
+            val listener = listenerCaptor.value
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+            // current user is trusted.
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+            // some other user is not trusted.
+            listener.onTrustChanged(false, false, users[1].id, 0, emptyList())
+
+            assertThat(isCurrentUserTrusted()).isTrue()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_whenTrustChangesForCurrentUser_emitsNewValue() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            userRepository.setSelectedUserInfo(users[0])
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isTrue()
+
+            listener.onTrustChanged(false, true, users[0].id, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            userRepository.setSelectedUserInfo(users[0])
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+            userRepository.setSelectedUserInfo(users[1])
+
+            assertThat(isCurrentUserTrusted()).isFalse()
+        }
+
+    @Test
+    fun isCurrentUserTrusted_trustChangesFirstBeforeUserInfoChanges_emitsCorrectValue() =
+        testScope.runTest {
+            runCurrent()
+            verify(trustManager).registerTrustListener(listenerCaptor.capture())
+            val listener = listenerCaptor.value
+            val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+            listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+            assertThat(isCurrentUserTrusted()).isFalse()
+
+            userRepository.setSelectedUserInfo(users[0])
+
+            assertThat(isCurrentUserTrusted()).isTrue()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 1da7241..8caf60f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -22,8 +22,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
@@ -45,7 +47,9 @@
 class AlternateBouncerInteractorTest : SysuiTestCase() {
     private lateinit var underTest: AlternateBouncerInteractor
     private lateinit var bouncerRepository: KeyguardBouncerRepository
-    private lateinit var biometricRepository: FakeBiometricRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var deviceEntryFingerprintAuthRepository:
+        FakeDeviceEntryFingerprintAuthRepository
     @Mock private lateinit var systemClock: SystemClock
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var bouncerLogger: TableLogBuffer
@@ -55,18 +59,20 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         bouncerRepository =
-            KeyguardBouncerRepository(
+            KeyguardBouncerRepositoryImpl(
                 mock(ViewMediatorCallback::class.java),
                 FakeSystemClock(),
                 TestCoroutineScope(),
                 bouncerLogger,
             )
-        biometricRepository = FakeBiometricRepository()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
         underTest =
             AlternateBouncerInteractor(
                 bouncerRepository,
-                biometricRepository,
+                biometricSettingsRepository,
+                deviceEntryFingerprintAuthRepository,
                 systemClock,
                 keyguardUpdateMonitor,
                 featureFlags,
@@ -90,7 +96,7 @@
     @Test
     fun canShowAlternateBouncerForFingerprint_noFingerprintsEnrolled() {
         givenCanShowAlternateBouncer()
-        biometricRepository.setFingerprintEnrolled(false)
+        biometricSettingsRepository.setFingerprintEnrolled(false)
 
         assertFalse(underTest.canShowAlternateBouncerForFingerprint())
     }
@@ -98,7 +104,7 @@
     @Test
     fun canShowAlternateBouncerForFingerprint_strongBiometricNotAllowed() {
         givenCanShowAlternateBouncer()
-        biometricRepository.setStrongBiometricAllowed(false)
+        biometricSettingsRepository.setStrongBiometricAllowed(false)
 
         assertFalse(underTest.canShowAlternateBouncerForFingerprint())
     }
@@ -106,7 +112,15 @@
     @Test
     fun canShowAlternateBouncerForFingerprint_devicePolicyDoesNotAllowFingerprint() {
         givenCanShowAlternateBouncer()
-        biometricRepository.setFingerprintEnabledByDevicePolicy(false)
+        biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(false)
+
+        assertFalse(underTest.canShowAlternateBouncerForFingerprint())
+    }
+
+    @Test
+    fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
+        givenCanShowAlternateBouncer()
+        deviceEntryFingerprintAuthRepository.setLockedOut(true)
 
         assertFalse(underTest.canShowAlternateBouncerForFingerprint())
     }
@@ -145,12 +159,13 @@
 
     private fun givenCanShowAlternateBouncer() {
         bouncerRepository.setAlternateBouncerUIAvailable(true)
-        biometricRepository.setFingerprintEnrolled(true)
-        biometricRepository.setStrongBiometricAllowed(true)
-        biometricRepository.setFingerprintEnabledByDevicePolicy(true)
+        biometricSettingsRepository.setFingerprintEnrolled(true)
+        biometricSettingsRepository.setStrongBiometricAllowed(true)
+        biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
+        deviceEntryFingerprintAuthRepository.setLockedOut(false)
     }
 
     private fun givenCannotShowAlternateBouncer() {
-        biometricRepository.setFingerprintEnrolled(false)
+        biometricSettingsRepository.setFingerprintEnrolled(false)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
new file mode 100644
index 0000000..9d60b16
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -0,0 +1,213 @@
+/*
+ * 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.systemui.keyguard.domain.interactor
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardLongPressInteractorTest : SysuiTestCase() {
+
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var logger: UiEventLogger
+
+    private lateinit var underTest: KeyguardLongPressInteractor
+
+    private lateinit var testScope: TestScope
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        runBlocking { createUnderTest() }
+    }
+
+    @Test
+    fun isEnabled() =
+        testScope.runTest {
+            val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled)
+            KeyguardState.values().forEach { keyguardState ->
+                setUpState(
+                    keyguardState = keyguardState,
+                )
+
+                if (keyguardState == KeyguardState.LOCKSCREEN) {
+                    assertThat(isEnabled()).isTrue()
+                } else {
+                    assertThat(isEnabled()).isFalse()
+                }
+            }
+        }
+
+    @Test
+    fun `isEnabled - always false when quick settings are visible`() =
+        testScope.runTest {
+            val isEnabled = collectLastValue(underTest.isLongPressHandlingEnabled)
+            KeyguardState.values().forEach { keyguardState ->
+                setUpState(
+                    keyguardState = keyguardState,
+                    isQuickSettingsVisible = true,
+                )
+
+                assertThat(isEnabled()).isFalse()
+            }
+        }
+
+    @Test
+    fun `long-pressed - pop-up clicked - starts activity`() =
+        testScope.runTest {
+            val menu = collectLastValue(underTest.menu)
+            runCurrent()
+
+            val x = 100
+            val y = 123
+            underTest.onLongPress(x, y)
+            assertThat(menu()).isNotNull()
+            assertThat(menu()?.position?.x).isEqualTo(x)
+            assertThat(menu()?.position?.y).isEqualTo(y)
+
+            menu()?.onClicked?.invoke()
+
+            assertThat(menu()).isNull()
+            verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+        }
+
+    @Test
+    fun `long-pressed - pop-up dismissed - never starts activity`() =
+        testScope.runTest {
+            val menu = collectLastValue(underTest.menu)
+            runCurrent()
+
+            menu()?.onDismissed?.invoke()
+
+            assertThat(menu()).isNull()
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+        }
+
+    @Suppress("DEPRECATION") // We're okay using ACTION_CLOSE_SYSTEM_DIALOGS on system UI.
+    @Test
+    fun `long pressed - close dialogs broadcast received - popup dismissed`() =
+        testScope.runTest {
+            val menu = collectLastValue(underTest.menu)
+            runCurrent()
+
+            underTest.onLongPress(123, 456)
+            assertThat(menu()).isNotNull()
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { broadcastReceiver ->
+                broadcastReceiver.onReceive(context, Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
+            }
+
+            assertThat(menu()).isNull()
+        }
+
+    @Test
+    fun `logs when menu is shown`() =
+        testScope.runTest {
+            collectLastValue(underTest.menu)
+            runCurrent()
+
+            underTest.onLongPress(100, 123)
+
+            verify(logger)
+                .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
+        }
+
+    @Test
+    fun `logs when menu is clicked`() =
+        testScope.runTest {
+            val menu = collectLastValue(underTest.menu)
+            runCurrent()
+
+            underTest.onLongPress(100, 123)
+            menu()?.onClicked?.invoke()
+
+            verify(logger)
+                .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
+        }
+
+    private suspend fun createUnderTest(
+        isLongPressFeatureEnabled: Boolean = true,
+        isRevampedWppFeatureEnabled: Boolean = true,
+    ) {
+        testScope = TestScope()
+        keyguardRepository = FakeKeyguardRepository()
+        keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+
+        underTest =
+            KeyguardLongPressInteractor(
+                unsafeContext = context,
+                scope = testScope.backgroundScope,
+                transitionInteractor =
+                    KeyguardTransitionInteractor(
+                        repository = keyguardTransitionRepository,
+                    ),
+                repository = keyguardRepository,
+                activityStarter = activityStarter,
+                logger = logger,
+                featureFlags =
+                    FakeFeatureFlags().apply {
+                        set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
+                        set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled)
+                    },
+                broadcastDispatcher = fakeBroadcastDispatcher,
+            )
+        setUpState()
+    }
+
+    private suspend fun setUpState(
+        keyguardState: KeyguardState = KeyguardState.LOCKSCREEN,
+        isQuickSettingsVisible: Boolean = false,
+    ) {
+        keyguardTransitionRepository.sendTransitionStep(
+            TransitionStep(
+                to = keyguardState,
+            ),
+        )
+        keyguardRepository.setQuickSettingsVisible(isVisible = isQuickSettingsVisible)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index a1b6d47..702f3763 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -17,12 +17,14 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -56,6 +58,7 @@
  */
 @SmallTest
 @RunWith(JUnit4::class)
+@FlakyTest(bugId = 265303901)
 class KeyguardTransitionScenariosTest : SysuiTestCase() {
     private lateinit var testScope: TestScope
 
@@ -435,6 +438,43 @@
         }
 
     @Test
+    fun `DOZING to GONE`() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DOZING
+            runner.startTransition(
+                testScope,
+                TransitionInfo(
+                    ownerName = "",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.DOZING,
+                    animator =
+                        ValueAnimator().apply {
+                            duration = 10
+                            interpolator = Interpolators.LINEAR
+                        },
+                )
+            )
+            runCurrent()
+            reset(mockTransitionRepository)
+
+            // WHEN biometrics succeeds with wake and unlock mode
+            keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(mockTransitionRepository).startTransition(capture())
+                }
+            // THEN a transition to DOZING should occur
+            assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
+            assertThat(info.to).isEqualTo(KeyguardState.GONE)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun `GONE to DOZING`() =
         testScope.runTest {
             // GIVEN a device with AOD not available
@@ -517,6 +557,43 @@
         }
 
     @Test
+    fun `GONE to LOCKSREEN`() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to GONE
+            runner.startTransition(
+                testScope,
+                TransitionInfo(
+                    ownerName = "",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    animator =
+                        ValueAnimator().apply {
+                            duration = 10
+                            interpolator = Interpolators.LINEAR
+                        },
+                )
+            )
+            runCurrent()
+            reset(mockTransitionRepository)
+
+            // WHEN the keyguard starts to show
+            keyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(mockTransitionRepository).startTransition(capture())
+                }
+            // THEN a transition to AOD should occur
+            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.GONE)
+            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun `GONE to DREAMING`() =
         testScope.runTest {
             // GIVEN a device that is not dreaming or dozing
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
index db9c4e7..fbfeca9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -19,7 +19,6 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.KeyguardBouncer
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,8 +33,10 @@
     private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
     @Mock
     private lateinit var mPrimaryBouncerExpansionCallback:
-        KeyguardBouncer.PrimaryBouncerExpansionCallback
-    @Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
+        PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
+    @Mock
+    private lateinit var keyguardResetCallback:
+        PrimaryBouncerCallbackInteractor.KeyguardResetCallback
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 7f48ea1..c5e0252 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -68,13 +68,13 @@
     @Mock private lateinit var keyguardBypassController: KeyguardBypassController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
-    private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
+    private lateinit var underTest: PrimaryBouncerInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         DejankUtils.setImmediate(true)
-        mPrimaryBouncerInteractor =
+        underTest =
             PrimaryBouncerInteractor(
                 repository,
                 bouncerView,
@@ -94,7 +94,7 @@
 
     @Test
     fun testShow_isScrimmed() {
-        mPrimaryBouncerInteractor.show(true)
+        underTest.show(true)
         verify(repository).setOnScreenTurnedOff(false)
         verify(repository).setKeyguardAuthenticated(null)
         verify(repository).setPrimaryHide(false)
@@ -124,7 +124,7 @@
 
     @Test
     fun testHide() {
-        mPrimaryBouncerInteractor.hide()
+        underTest.hide()
         verify(falsingCollector).onBouncerHidden()
         verify(keyguardStateController).notifyBouncerShowing(false)
         verify(repository).setPrimaryShowingSoon(false)
@@ -137,7 +137,7 @@
     @Test
     fun testExpansion() {
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        mPrimaryBouncerInteractor.setPanelExpansion(0.6f)
+        underTest.setPanelExpansion(0.6f)
         verify(repository).setPanelExpansion(0.6f)
         verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
     }
@@ -146,7 +146,7 @@
     fun testExpansion_fullyShown() {
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
+        underTest.setPanelExpansion(EXPANSION_VISIBLE)
         verify(falsingCollector).onBouncerShown()
         verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
     }
@@ -155,7 +155,7 @@
     fun testExpansion_fullyHidden() {
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
+        underTest.setPanelExpansion(EXPANSION_HIDDEN)
         verify(repository).setPrimaryVisible(false)
         verify(repository).setPrimaryShow(null)
         verify(repository).setPrimaryHide(true)
@@ -167,7 +167,7 @@
     @Test
     fun testExpansion_startingToHide() {
         `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        mPrimaryBouncerInteractor.setPanelExpansion(0.1f)
+        underTest.setPanelExpansion(0.1f)
         verify(repository).setPrimaryStartingToHide(true)
         verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
     }
@@ -175,7 +175,7 @@
     @Test
     fun testShowMessage() {
         val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
-        mPrimaryBouncerInteractor.showMessage("abc", null)
+        underTest.showMessage("abc", null)
         verify(repository).setShowMessage(argCaptor.capture())
         assertThat(argCaptor.value.message).isEqualTo("abc")
     }
@@ -184,62 +184,62 @@
     fun testDismissAction() {
         val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
         val cancelAction = mock(Runnable::class.java)
-        mPrimaryBouncerInteractor.setDismissAction(onDismissAction, cancelAction)
+        underTest.setDismissAction(onDismissAction, cancelAction)
         verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
     }
 
     @Test
     fun testUpdateResources() {
-        mPrimaryBouncerInteractor.updateResources()
+        underTest.updateResources()
         verify(repository).setResourceUpdateRequests(true)
     }
 
     @Test
     fun testNotifyKeyguardAuthenticated() {
-        mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(true)
+        underTest.notifyKeyguardAuthenticated(true)
         verify(repository).setKeyguardAuthenticated(true)
     }
 
     @Test
     fun testNotifyShowedMessage() {
-        mPrimaryBouncerInteractor.onMessageShown()
+        underTest.onMessageShown()
         verify(repository).setShowMessage(null)
     }
 
     @Test
     fun testOnScreenTurnedOff() {
-        mPrimaryBouncerInteractor.onScreenTurnedOff()
+        underTest.onScreenTurnedOff()
         verify(repository).setOnScreenTurnedOff(true)
     }
 
     @Test
     fun testSetKeyguardPosition() {
-        mPrimaryBouncerInteractor.setKeyguardPosition(0f)
+        underTest.setKeyguardPosition(0f)
         verify(repository).setKeyguardPosition(0f)
     }
 
     @Test
     fun testNotifyKeyguardAuthenticatedHandled() {
-        mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
+        underTest.notifyKeyguardAuthenticatedHandled()
         verify(repository).setKeyguardAuthenticated(null)
     }
 
     @Test
     fun testNotifyUpdatedResources() {
-        mPrimaryBouncerInteractor.notifyUpdatedResources()
+        underTest.notifyUpdatedResources()
         verify(repository).setResourceUpdateRequests(false)
     }
 
     @Test
     fun testSetBackButtonEnabled() {
-        mPrimaryBouncerInteractor.setBackButtonEnabled(true)
+        underTest.setBackButtonEnabled(true)
         verify(repository).setIsBackButtonEnabled(true)
     }
 
     @Test
     fun testStartDisappearAnimation() {
         val runnable = mock(Runnable::class.java)
-        mPrimaryBouncerInteractor.startDisappearAnimation(runnable)
+        underTest.startDisappearAnimation(runnable)
         verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
     }
 
@@ -248,42 +248,42 @@
         `when`(repository.primaryBouncerVisible.value).thenReturn(true)
         `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isTrue()
+        assertThat(underTest.isFullyShowing()).isTrue()
         `when`(repository.primaryBouncerVisible.value).thenReturn(false)
-        assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isFalse()
+        assertThat(underTest.isFullyShowing()).isFalse()
     }
 
     @Test
     fun testIsScrimmed() {
         `when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
-        assertThat(mPrimaryBouncerInteractor.isScrimmed()).isTrue()
+        assertThat(underTest.isScrimmed()).isTrue()
         `when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
-        assertThat(mPrimaryBouncerInteractor.isScrimmed()).isFalse()
+        assertThat(underTest.isScrimmed()).isFalse()
     }
 
     @Test
     fun testIsInTransit() {
         `when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
-        assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+        assertThat(underTest.isInTransit()).isTrue()
         `when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
-        assertThat(mPrimaryBouncerInteractor.isInTransit()).isFalse()
+        assertThat(underTest.isInTransit()).isFalse()
         `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
+        assertThat(underTest.isInTransit()).isTrue()
     }
 
     @Test
     fun testIsAnimatingAway() {
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
-        assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isTrue()
+        assertThat(underTest.isAnimatingAway()).isTrue()
         `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
-        assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isFalse()
+        assertThat(underTest.isAnimatingAway()).isFalse()
     }
 
     @Test
     fun testWillDismissWithAction() {
         `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
-        assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isTrue()
+        assertThat(underTest.willDismissWithAction()).isTrue()
         `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
-        assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isFalse()
+        assertThat(underTest.willDismissWithAction()).isFalse()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
new file mode 100644
index 0000000..ea7bc91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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.systemui.keyguard.domain.interactor
+
+import android.os.Looper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.utils.os.FakeHandler
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
+    private lateinit var repository: FakeKeyguardBouncerRepository
+    @Mock private lateinit var bouncerView: BouncerView
+    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
+    @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
+    @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    private val mainHandler = FakeHandler(Looper.getMainLooper())
+    private lateinit var underTest: PrimaryBouncerInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        repository = FakeKeyguardBouncerRepository()
+        underTest =
+            PrimaryBouncerInteractor(
+                repository,
+                bouncerView,
+                mainHandler,
+                keyguardStateController,
+                keyguardSecurityModel,
+                primaryBouncerCallbackInteractor,
+                falsingCollector,
+                dismissCallbackRegistry,
+                keyguardBypassController,
+                keyguardUpdateMonitor,
+            )
+    }
+
+    @Test
+    fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
+        val isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryVisible(true)
+        repository.setPanelExpansion(0.15f)
+
+        assertThat(isInteractable()).isFalse()
+    }
+
+    @Test
+    fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
+        val isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryVisible(false)
+        repository.setPanelExpansion(0.05f)
+
+        assertThat(isInteractable()).isFalse()
+    }
+
+    @Test
+    fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
+        var isInteractable = collectLastValue(underTest.isInteractable)
+
+        repository.setPrimaryVisible(true)
+        repository.setPanelExpansion(0.09f)
+
+        assertThat(isInteractable()).isTrue()
+
+        repository.setPanelExpansion(0.12f)
+        assertThat(isInteractable()).isFalse()
+
+        repository.setPanelExpansion(0f)
+        assertThat(isInteractable()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
new file mode 100644
index 0000000..a5b78b74
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.systemui.keyguard.ui
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
+    private lateinit var underTest: KeyguardTransitionAnimationFlow
+    private lateinit var repository: FakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        repository = FakeKeyguardTransitionRepository()
+        underTest =
+            KeyguardTransitionAnimationFlow(
+                1000.milliseconds,
+                repository.transitions,
+            )
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun zeroDurationThrowsException() = runTest {
+        val flow = underTest.createFlow(duration = 0.milliseconds, onStep = { it })
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun startTimePlusDurationGreaterThanTransitionDurationThrowsException() = runTest {
+        val flow =
+            underTest.createFlow(
+                startTime = 300.milliseconds,
+                duration = 800.milliseconds,
+                onStep = { it }
+            )
+    }
+
+    @Test
+    fun onFinishRunsWhenSpecified() = runTest {
+        val flow =
+            underTest.createFlow(
+                duration = 100.milliseconds,
+                onStep = { it },
+                onFinish = { 10f },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+        assertThat(animationValues()).isEqualTo(10f)
+    }
+
+    @Test
+    fun onCancelRunsWhenSpecified() = runTest {
+        val flow =
+            underTest.createFlow(
+                duration = 100.milliseconds,
+                onStep = { it },
+                onCancel = { 100f },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(0.5f, TransitionState.CANCELED))
+        assertThat(animationValues()).isEqualTo(100f)
+    }
+
+    @Test
+    fun usesStartTime() = runTest {
+        val flow =
+            underTest.createFlow(
+                startTime = 500.milliseconds,
+                duration = 500.milliseconds,
+                onStep = { it },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+        assertThat(animationValues()).isEqualTo(0f)
+
+        // Should not emit a value
+        repository.sendTransitionStep(step(0.1f, TransitionState.RUNNING))
+
+        repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 0f)
+        repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 0.2f)
+        repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 0.6f)
+        repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 1f)
+    }
+
+    @Test
+    fun usesInterpolator() = runTest {
+        val flow =
+            underTest.createFlow(
+                duration = 1000.milliseconds,
+                interpolator = EMPHASIZED_ACCELERATE,
+                onStep = { it },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0f))
+        repository.sendTransitionStep(step(0.5f, TransitionState.RUNNING))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.5f))
+        repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.6f))
+        repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(0.8f))
+        repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+        assertFloat(animationValues(), EMPHASIZED_ACCELERATE.getInterpolation(1f))
+    }
+
+    @Test
+    fun usesOnStepToDoubleValue() = runTest {
+        val flow =
+            underTest.createFlow(
+                duration = 1000.milliseconds,
+                onStep = { it * 2 },
+            )
+        var animationValues = collectLastValue(flow)
+        repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+        assertFloat(animationValues(), 0f)
+        repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 0.6f)
+        repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 1.2f)
+        repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 1.6f)
+        repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+        assertFloat(animationValues(), 2f)
+    }
+
+    private fun assertFloat(actual: Float?, expected: Float) {
+        assertThat(actual!!).isWithin(0.01f).of(expected)
+    }
+
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.GONE,
+            to = KeyguardState.DREAMING,
+            value = value,
+            transitionState = state,
+            ownerName = "GoneToDreamingTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 5571663..06e397d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -18,19 +18,13 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -46,6 +40,7 @@
 class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: DreamingToLockscreenTransitionViewModel
     private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var transitionAnimation: KeyguardTransitionAnimationFlow
 
     @Before
     fun setUp() {
@@ -63,32 +58,18 @@
             val job =
                 underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
+            // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
+            repository.sendTransitionStep(step(0.6f))
+            // ...up to here
+            repository.sendTransitionStep(step(0.8f))
             repository.sendTransitionStep(step(1f))
 
-            // Only 3 values should be present, since the dream overlay runs for a small fraction
-            // of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0f, DREAM_OVERLAY_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.3f, DREAM_OVERLAY_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.5f, DREAM_OVERLAY_TRANSLATION_Y)
-                    ) * pixels
-                )
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
 
             job.cancel()
         }
@@ -100,16 +81,18 @@
 
             val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
 
+            // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.5f))
+            // ...up to here
             repository.sendTransitionStep(step(1f))
 
             // Only two values should be present, since the dream overlay runs for a small fraction
             // of the overall animation time
-            assertThat(values.size).isEqualTo(2)
-            assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA))
-            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA))
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -121,19 +104,15 @@
 
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
-            // Should start running here...
             repository.sendTransitionStep(step(0.2f))
             repository.sendTransitionStep(step(0.3f))
-            // ...up to here
             repository.sendTransitionStep(step(1f))
 
-            // Only two values should be present, since the dream overlay runs for a small fraction
-            // of the overall animation time
-            assertThat(values.size).isEqualTo(2)
-            assertThat(values[0]).isEqualTo(animValue(0.2f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -147,58 +126,27 @@
             val job =
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
             repository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[3])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(1f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
 
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_LOCKSCREEN_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
-
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
-
-    private fun step(value: Float): TransitionStep {
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
         return TransitionStep(
             from = KeyguardState.DREAMING,
             to = KeyguardState.LOCKSCREEN,
             value = value,
-            transitionState = TransitionState.RUNNING,
+            transitionState = state,
             ownerName = "DreamingToLockscreenTransitionViewModelTest"
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 7fa204b..14c3b50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -59,20 +55,18 @@
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.2f))
-            // ...up to here
             repository.sendTransitionStep(step(0.3f))
+            // ...up to here
             repository.sendTransitionStep(step(1f))
 
             // Only three values should be present, since the dream overlay runs for a small
-            // fraction
-            // of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
-            assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+            // fraction of the overall animation time
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -87,45 +81,19 @@
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
-            // ...up to here
-            repository.sendTransitionStep(step(1f))
             // And a final reset event on CANCEL
             repository.sendTransitionStep(step(0.8f, TransitionState.CANCELED))
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[3]).isEqualTo(0f)
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_DREAMING_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
-
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
-
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 022afdd..4b04b7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -234,7 +234,10 @@
     @Test
     fun `startButton - in preview mode - visible even when keyguard not showing`() =
         testScope.runTest {
-            underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+            underTest.enablePreviewMode(
+                initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                shouldHighlightSelectedAffordance = true,
+            )
             repository.setKeyguardShowing(false)
             val latest = collectLastValue(underTest.startButton)
 
@@ -263,6 +266,7 @@
                         icon = icon,
                         canShowWhileLocked = false,
                         intent = Intent("action"),
+                        isSelected = true,
                     ),
                 configKey = configKey,
             )
@@ -270,6 +274,60 @@
         }
 
     @Test
+    fun `endButton - in higlighted preview mode - dimmed when other is selected`() =
+        testScope.runTest {
+            underTest.enablePreviewMode(
+                initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                shouldHighlightSelectedAffordance = true,
+            )
+            repository.setKeyguardShowing(false)
+            val startButton = collectLastValue(underTest.startButton)
+            val endButton = collectLastValue(underTest.endButton)
+
+            val icon: Icon = mock()
+            setUpQuickAffordanceModel(
+                position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                testConfig =
+                    TestConfig(
+                        isVisible = true,
+                        isClickable = true,
+                        isActivated = true,
+                        icon = icon,
+                        canShowWhileLocked = false,
+                        intent = Intent("action"),
+                    ),
+            )
+            val configKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                    testConfig =
+                        TestConfig(
+                            isVisible = true,
+                            isClickable = true,
+                            isActivated = true,
+                            icon = icon,
+                            canShowWhileLocked = false,
+                            intent = Intent("action"),
+                        ),
+                )
+
+            assertQuickAffordanceViewModel(
+                viewModel = endButton(),
+                testConfig =
+                    TestConfig(
+                        isVisible = true,
+                        isClickable = false,
+                        isActivated = true,
+                        icon = icon,
+                        canShowWhileLocked = false,
+                        intent = Intent("action"),
+                        isDimmed = true,
+                    ),
+                configKey = configKey,
+            )
+        }
+
+    @Test
     fun `endButton - present - visible model - do nothing on click`() =
         testScope.runTest {
             repository.setKeyguardShowing(true)
@@ -377,7 +435,10 @@
     @Test
     fun `alpha - in preview mode - does not change`() =
         testScope.runTest {
-            underTest.enablePreviewMode(null)
+            underTest.enablePreviewMode(
+                initiallySelectedSlotId = null,
+                shouldHighlightSelectedAffordance = false,
+            )
             val value = collectLastValue(underTest.alpha)
 
             assertThat(value()).isEqualTo(1f)
@@ -639,6 +700,8 @@
         assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
         assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
         assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
+        assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
+        assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
         if (testConfig.isVisible) {
             assertThat(viewModel.icon).isEqualTo(testConfig.icon)
             viewModel.onClicked.invoke(
@@ -664,6 +727,8 @@
         val icon: Icon? = null,
         val canShowWhileLocked: Boolean = false,
         val intent: Intent? = null,
+        val isSelected: Boolean = false,
+        val isDimmed: Boolean = false,
     ) {
         init {
             check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index 539fc2c..ed31dc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.2f))
-            // ...up to here
             repository.sendTransitionStep(step(0.3f))
+            // ...up to here
             repository.sendTransitionStep(step(1f))
 
             // Only three values should be present, since the dream overlay runs for a small
             // fraction of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
-            assertThat(values[2]).isEqualTo(1f - animValue(0.2f, LOCKSCREEN_ALPHA))
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -85,47 +80,22 @@
             val job =
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
-            // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
-            // ...up to here
             repository.sendTransitionStep(step(1f))
             // And a final reset event on FINISHED
             repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[3]).isEqualTo(0f)
+            assertThat(values.size).isEqualTo(6)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+            // Validate finished value
+            assertThat(values[5]).isEqualTo(0f)
 
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_DREAMING_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
-
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
-
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 759345f..458b315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -59,19 +55,18 @@
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.1f))
             repository.sendTransitionStep(step(0.4f))
-            // ...up to here
             repository.sendTransitionStep(step(0.7f))
+            // ...up to here
             repository.sendTransitionStep(step(1f))
 
             // Only 3 values should be present, since the dream overlay runs for a small fraction
             // of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0]).isEqualTo(1f - animValue(0f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, LOCKSCREEN_ALPHA))
-            assertThat(values[2]).isEqualTo(1f - animValue(0.4f, LOCKSCREEN_ALPHA))
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -86,54 +81,51 @@
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
             // Should start running here...
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
             repository.sendTransitionStep(step(1f))
             // ...up to here
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
-            assertThat(values[3])
-                .isEqualTo(
-                    EMPHASIZED_ACCELERATE.getInterpolation(
-                        animValue(1f, LOCKSCREEN_TRANSLATION_Y)
-                    ) * pixels
-                )
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_OCCLUDED_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
+    @Test
+    fun lockscreenTranslationYIsCanceled() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
 
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
+            val pixels = 100
+            val job =
+                underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
-    private fun step(value: Float): TransitionStep {
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
+
+            // Cancel will reset the translation
+            assertThat(values[3]).isEqualTo(0)
+
+            job.cancel()
+        }
+
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING,
+    ): TransitionStep {
         return TransitionStep(
             from = KeyguardState.LOCKSCREEN,
             to = KeyguardState.OCCLUDED,
             value = value,
-            transitionState = TransitionState.RUNNING,
+            transitionState = state,
             ownerName = "LockscreenToOccludedTransitionViewModelTest"
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index 98d292d..a36214e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -18,16 +18,12 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
-import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -58,21 +54,19 @@
 
             val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
 
-            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.1f))
             // Should start running here...
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.4f))
             repository.sendTransitionStep(step(0.5f))
-            // ...up to here
             repository.sendTransitionStep(step(0.6f))
+            // ...up to here
+            repository.sendTransitionStep(step(0.8f))
             repository.sendTransitionStep(step(1f))
 
-            // Only two values should be present, since the dream overlay runs for a small fraction
-            // of the overall animation time
-            assertThat(values.size).isEqualTo(3)
-            assertThat(values[0]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
-            assertThat(values[1]).isEqualTo(animValue(0.4f, LOCKSCREEN_ALPHA))
-            assertThat(values[2]).isEqualTo(animValue(0.5f, LOCKSCREEN_ALPHA))
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
 
             job.cancel()
         }
@@ -86,58 +80,27 @@
             val job =
                 underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
 
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
             repository.sendTransitionStep(step(0f))
             repository.sendTransitionStep(step(0.3f))
             repository.sendTransitionStep(step(0.5f))
             repository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(4)
-            assertThat(values[0])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[1])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[2])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
-            assertThat(values[3])
-                .isEqualTo(
-                    -pixels +
-                        EMPHASIZED_DECELERATE.getInterpolation(
-                            animValue(1f, LOCKSCREEN_TRANSLATION_Y)
-                        ) * pixels
-                )
+            assertThat(values.size).isEqualTo(5)
+            values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
 
             job.cancel()
         }
 
-    private fun animValue(stepValue: Float, params: AnimationParams): Float {
-        val totalDuration = TO_LOCKSCREEN_DURATION
-        val startValue = (params.startTime / totalDuration).toFloat()
-
-        val multiplier = (totalDuration / params.duration).toFloat()
-        return (stepValue - startValue) * multiplier
-    }
-
-    private fun step(value: Float): TransitionStep {
+    private fun step(
+        value: Float,
+        state: TransitionState = TransitionState.RUNNING
+    ): TransitionStep {
         return TransitionStep(
             from = KeyguardState.OCCLUDED,
             to = KeyguardState.LOCKSCREEN,
             value = value,
-            transitionState = TransitionState.RUNNING,
+            transitionState = state,
             ownerName = "OccludedToLockscreenTransitionViewModelTest"
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index c88f84a..54fc493 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -71,7 +71,7 @@
         waitUntilComplete(info.animator!!)
     }
 
-    suspend private fun waitUntilComplete(animator: ValueAnimator) {
+    private suspend fun waitUntilComplete(animator: ValueAnimator) {
         withContext(Dispatchers.Main) {
             val startTime = System.currentTimeMillis()
             while (!isTerminated && animator.isRunning()) {
@@ -96,6 +96,6 @@
     override fun setFrameDelay(delay: Long) {}
 
     companion object {
-        private const val MAX_TEST_DURATION = 100L
+        private const val MAX_TEST_DURATION = 200L
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
index 1d6e980..670f117 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
@@ -113,5 +113,6 @@
         recommendations = emptyList(),
         dismissIntent = null,
         headphoneConnectionTimeMillis = 0,
-        instanceId = InstanceId.fakeInstanceId(-1)
+        instanceId = InstanceId.fakeInstanceId(-1),
+        expiryTimeMs = 0,
     )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index c0639f3..0a5b124 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -79,7 +79,7 @@
                 USER_ID, true, APP, null, ARTIST, TITLE, null,
                 new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
                 MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
-                InstanceId.fakeInstanceId(-1), -1, false);
+                InstanceId.fakeInstanceId(-1), -1, false, null);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 9d33e6f..eb6235c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -27,11 +27,13 @@
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.ui.MediaPlayerData
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
@@ -40,11 +42,11 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 private const val KEY = "TEST_KEY"
@@ -72,6 +74,7 @@
     @Mock private lateinit var smartspaceData: SmartspaceMediaData
     @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
     @Mock private lateinit var logger: MediaUiEventLogger
+    @Mock private lateinit var mediaFlags: MediaFlags
 
     private lateinit var mediaDataFilter: MediaDataFilter
     private lateinit var dataMain: MediaData
@@ -82,6 +85,7 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         MediaPlayerData.clear()
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         mediaDataFilter =
             MediaDataFilter(
                 context,
@@ -90,7 +94,8 @@
                 lockscreenUserManager,
                 executor,
                 clock,
-                logger
+                logger,
+                mediaFlags
             )
         mediaDataFilter.mediaDataManager = mediaDataManager
         mediaDataFilter.addListener(listener)
@@ -108,19 +113,20 @@
             )
         dataGuest = dataMain.copy(userId = USER_GUEST)
 
-        `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
-        `when`(smartspaceData.isActive).thenReturn(true)
-        `when`(smartspaceData.isValid()).thenReturn(true)
-        `when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
-        `when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem))
-        `when`(smartspaceData.headphoneConnectionTimeMillis)
+        whenever(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
+        whenever(smartspaceData.isActive).thenReturn(true)
+        whenever(smartspaceData.isValid()).thenReturn(true)
+        whenever(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
+        whenever(smartspaceData.recommendations)
+            .thenReturn(listOf(smartspaceMediaRecommendationItem))
+        whenever(smartspaceData.headphoneConnectionTimeMillis)
             .thenReturn(clock.currentTimeMillis() - 100)
-        `when`(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
+        whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
     }
 
     private fun setUser(id: Int) {
-        `when`(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
-        `when`(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
+        whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+        whenever(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
         mediaDataFilter.handleUserSwitched(id)
     }
 
@@ -277,7 +283,7 @@
 
     @Test
     fun hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalse() {
-        `when`(smartspaceData.isActive).thenReturn(false)
+        whenever(smartspaceData.isActive).thenReturn(false)
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
@@ -285,7 +291,7 @@
 
     @Test
     fun hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalse() {
-        `when`(smartspaceData.isValid()).thenReturn(false)
+        whenever(smartspaceData.isValid()).thenReturn(false)
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
@@ -293,8 +299,8 @@
 
     @Test
     fun hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTrue() {
-        `when`(smartspaceData.isActive).thenReturn(true)
-        `when`(smartspaceData.isValid()).thenReturn(true)
+        whenever(smartspaceData.isActive).thenReturn(true)
+        whenever(smartspaceData.isValid()).thenReturn(true)
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
@@ -349,7 +355,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() {
-        `when`(smartspaceData.isActive).thenReturn(false)
+        whenever(smartspaceData.isActive).thenReturn(false)
 
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
@@ -379,7 +385,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() {
-        `when`(smartspaceData.isActive).thenReturn(false)
+        whenever(smartspaceData.isActive).thenReturn(false)
 
         val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
@@ -395,7 +401,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() {
-        `when`(smartspaceData.isActive).thenReturn(false)
+        whenever(smartspaceData.isActive).thenReturn(false)
 
         // WHEN we have media that was recently played, but not currently active
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
@@ -418,7 +424,7 @@
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() {
-        `when`(smartspaceData.isValid()).thenReturn(false)
+        whenever(smartspaceData.isValid()).thenReturn(false)
 
         // WHEN we have media that was recently played, but not currently active
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
@@ -513,4 +519,59 @@
         assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
+
+    @Test
+    fun testOnSmartspaceLoaded_persistentEnabled_isInactive_notifiesListeners() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        whenever(smartspaceData.isActive).thenReturn(false)
+
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue()
+    }
+
+    @Test
+    fun testOnSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        whenever(smartspaceData.isActive).thenReturn(false)
+
+        // If there is media that was recently played but inactive
+        val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+        mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+        verify(listener)
+            .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
+
+        // And an inactive recommendation is loaded
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+        // Smartspace is loaded but the media stays inactive
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+        verify(listener, never())
+            .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue()
+    }
+
+    @Test
+    fun testOnSwipeToDismiss_persistentEnabled_recommendationSetInactive() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+        val data =
+            EMPTY_SMARTSPACE_MEDIA_DATA.copy(
+                targetId = SMARTSPACE_KEY,
+                isActive = true,
+                packageName = SMARTSPACE_PACKAGE,
+                recommendations = listOf(smartspaceMediaRecommendationItem),
+            )
+        mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, data)
+        mediaDataFilter.onSwipeToDismiss()
+
+        verify(mediaDataManager).setRecommendationInactive(eq(SMARTSPACE_KEY))
+        verify(mediaDataManager, never())
+            .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 1687fdc..44e2fbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -46,6 +46,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
 import com.android.systemui.media.controls.resume.MediaResumeListener
@@ -82,6 +84,8 @@
 private const val KEY = "KEY"
 private const val KEY_2 = "KEY_2"
 private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
+private const val SMARTSPACE_CREATION_TIME = 1234L
+private const val SMARTSPACE_EXPIRY_TIME = 5678L
 private const val PACKAGE_NAME = "com.example.app"
 private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
 private const val APP_NAME = "SystemUI"
@@ -134,7 +138,8 @@
     private val clock = FakeSystemClock()
     @Mock private lateinit var tunerService: TunerService
     @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
-    @Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
+    @Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
+    @Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit>
     @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
 
     private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
@@ -184,6 +189,8 @@
             )
         verify(tunerService)
             .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
+        verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor)
+        verify(mediaTimeoutListener).sessionCallback = capture(sessionCallbackCaptor)
         session = MediaSession(context, "MediaDataManagerTestSession")
         mediaNotification =
             SbnBuilder().run {
@@ -227,9 +234,12 @@
         whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
         whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList)
-        whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
+        whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(SMARTSPACE_CREATION_TIME)
+        whenever(mediaSmartspaceTarget.expiryTimeMillis).thenReturn(SMARTSPACE_EXPIRY_TIME)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
         whenever(mediaFlags.isExplicitIndicatorEnabled()).thenReturn(true)
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
     }
 
@@ -547,6 +557,7 @@
         mediaDataManager.onNotificationAdded(KEY_2, mediaNotification)
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(2)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
+
         verify(listener)
             .onMediaDataLoaded(
                 eq(KEY),
@@ -558,9 +569,21 @@
             )
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
-        val resumableData = data.copy(resumeAction = Runnable {})
-        mediaDataManager.onMediaDataLoaded(KEY, null, resumableData)
-        mediaDataManager.onMediaDataLoaded(KEY_2, null, resumableData)
+
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(KEY_2),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        val data2 = mediaDataCaptor.value
+        assertThat(data2.resumption).isFalse()
+
+        mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+        mediaDataManager.onMediaDataLoaded(KEY_2, null, data2.copy(resumeAction = Runnable {}))
         reset(listener)
         // WHEN the first is removed
         mediaDataManager.onNotificationRemoved(KEY)
@@ -627,27 +650,8 @@
                 build()
             }
         val currentTime = clock.elapsedRealtime()
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        // THEN the media data indicates that it is for resumption
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
+        addResumeControlAndLoad(desc)
+
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -673,27 +677,8 @@
                 build()
             }
         val currentTime = clock.elapsedRealtime()
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        // THEN the media data indicates that it is for resumption
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
+        addResumeControlAndLoad(desc)
+
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -706,6 +691,84 @@
     }
 
     @Test
+    fun testAddResumptionControls_hasPartialProgress() {
+        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+        // WHEN resumption controls are added with partial progress
+        val progress = 0.5
+        val extras =
+            Bundle().apply {
+                putInt(
+                    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+                    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+                )
+                putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress)
+            }
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                setExtras(extras)
+                build()
+            }
+        addResumeControlAndLoad(desc)
+
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isTrue()
+        assertThat(data.resumeProgress).isEqualTo(progress)
+    }
+
+    @Test
+    fun testAddResumptionControls_hasNotPlayedProgress() {
+        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+        // WHEN resumption controls are added that have not been played
+        val extras =
+            Bundle().apply {
+                putInt(
+                    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+                    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
+                )
+            }
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                setExtras(extras)
+                build()
+            }
+        addResumeControlAndLoad(desc)
+
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isTrue()
+        assertThat(data.resumeProgress).isEqualTo(0)
+    }
+
+    @Test
+    fun testAddResumptionControls_hasFullProgress() {
+        whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+        // WHEN resumption controls are added with progress info
+        val extras =
+            Bundle().apply {
+                putInt(
+                    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+                    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
+                )
+            }
+        val desc =
+            MediaDescription.Builder().run {
+                setTitle(SESSION_TITLE)
+                setExtras(extras)
+                build()
+            }
+        addResumeControlAndLoad(desc)
+
+        // THEN the media data includes the progress
+        val data = mediaDataCaptor.value
+        assertThat(data.resumption).isTrue()
+        assertThat(data.resumeProgress).isEqualTo(1)
+    }
+
+    @Test
     fun testResumptionDisabled_dismissesResumeControls() {
         // WHEN there are resume controls and resumption is switched off
         val desc =
@@ -713,26 +776,8 @@
                 setTitle(SESSION_TITLE)
                 build()
             }
-        mediaDataManager.addResumptionControls(
-            USER_ID,
-            desc,
-            Runnable {},
-            session.sessionToken,
-            APP_NAME,
-            pendingIntent,
-            PACKAGE_NAME
-        )
-        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
-        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-        verify(listener)
-            .onMediaDataLoaded(
-                eq(PACKAGE_NAME),
-                eq(null),
-                capture(mediaDataCaptor),
-                eq(true),
-                eq(0),
-                eq(false)
-            )
+        addResumeControlAndLoad(desc)
+
         val data = mediaDataCaptor.value
         mediaDataManager.setMediaResumptionEnabled(false)
 
@@ -808,8 +853,9 @@
                         cardAction = mediaSmartspaceBaseAction,
                         recommendations = validRecommendationList,
                         dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = 1234L,
-                        instanceId = InstanceId.fakeInstanceId(instanceId)
+                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+                        instanceId = InstanceId.fakeInstanceId(instanceId),
+                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
                     )
                 ),
                 eq(false)
@@ -831,8 +877,9 @@
                         targetId = KEY_MEDIA_SMARTSPACE,
                         isActive = true,
                         dismissIntent = DISMISS_INTENT,
-                        headphoneConnectionTimeMillis = 1234L,
-                        instanceId = InstanceId.fakeInstanceId(instanceId)
+                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+                        instanceId = InstanceId.fakeInstanceId(instanceId),
+                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
                     )
                 ),
                 eq(false)
@@ -862,8 +909,9 @@
                         targetId = KEY_MEDIA_SMARTSPACE,
                         isActive = true,
                         dismissIntent = null,
-                        headphoneConnectionTimeMillis = 1234L,
-                        instanceId = InstanceId.fakeInstanceId(instanceId)
+                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+                        instanceId = InstanceId.fakeInstanceId(instanceId),
+                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
                     )
                 ),
                 eq(false)
@@ -892,6 +940,129 @@
     }
 
     @Test
+    fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+        val instanceId = instanceIdSequence.lastInstanceId
+
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(
+                eq(KEY_MEDIA_SMARTSPACE),
+                eq(
+                    SmartspaceMediaData(
+                        targetId = KEY_MEDIA_SMARTSPACE,
+                        isActive = true,
+                        packageName = PACKAGE_NAME,
+                        cardAction = mediaSmartspaceBaseAction,
+                        recommendations = validRecommendationList,
+                        dismissIntent = DISMISS_INTENT,
+                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+                        instanceId = InstanceId.fakeInstanceId(instanceId),
+                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+                    )
+                ),
+                eq(false)
+            )
+    }
+
+    @Test
+    fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        val extras =
+            Bundle().apply {
+                putString("package_name", PACKAGE_NAME)
+                putParcelable("dismiss_intent", DISMISS_INTENT)
+                putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
+            }
+        whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
+
+        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+        val instanceId = instanceIdSequence.lastInstanceId
+
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(
+                eq(KEY_MEDIA_SMARTSPACE),
+                eq(
+                    SmartspaceMediaData(
+                        targetId = KEY_MEDIA_SMARTSPACE,
+                        isActive = false,
+                        packageName = PACKAGE_NAME,
+                        cardAction = mediaSmartspaceBaseAction,
+                        recommendations = validRecommendationList,
+                        dismissIntent = DISMISS_INTENT,
+                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+                        instanceId = InstanceId.fakeInstanceId(instanceId),
+                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+                    )
+                ),
+                eq(false)
+            )
+    }
+
+    @Test
+    fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+        val instanceId = instanceIdSequence.lastInstanceId
+
+        smartspaceMediaDataProvider.onTargetsAvailable(listOf())
+        uiExecutor.advanceClockToLast()
+        uiExecutor.runAllReady()
+
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(
+                eq(KEY_MEDIA_SMARTSPACE),
+                eq(
+                    SmartspaceMediaData(
+                        targetId = KEY_MEDIA_SMARTSPACE,
+                        isActive = false,
+                        packageName = PACKAGE_NAME,
+                        cardAction = mediaSmartspaceBaseAction,
+                        recommendations = validRecommendationList,
+                        dismissIntent = DISMISS_INTENT,
+                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+                        instanceId = InstanceId.fakeInstanceId(instanceId),
+                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+                    )
+                ),
+                eq(false)
+            )
+        verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
+    }
+
+    @Test
+    fun testSetRecommendationInactive_notifiesListeners() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+        smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
+        val instanceId = instanceIdSequence.lastInstanceId
+
+        mediaDataManager.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
+        uiExecutor.advanceClockToLast()
+        uiExecutor.runAllReady()
+
+        verify(listener)
+            .onSmartspaceMediaDataLoaded(
+                eq(KEY_MEDIA_SMARTSPACE),
+                eq(
+                    SmartspaceMediaData(
+                        targetId = KEY_MEDIA_SMARTSPACE,
+                        isActive = false,
+                        packageName = PACKAGE_NAME,
+                        cardAction = mediaSmartspaceBaseAction,
+                        recommendations = validRecommendationList,
+                        dismissIntent = DISMISS_INTENT,
+                        headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
+                        instanceId = InstanceId.fakeInstanceId(instanceId),
+                        expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
+                    )
+                ),
+                eq(false)
+            )
+    }
+
+    @Test
     fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
         // WHEN media recommendation setting is off
         Settings.Secure.putInt(
@@ -1310,11 +1481,10 @@
     fun testPlaybackStateChange_keyExists_callsListener() {
         // Notification has been added
         addNotificationAndLoad()
-        verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
 
         // Callback gets an updated state
         val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
-        callbackCaptor.value.invoke(KEY, state)
+        stateCallbackCaptor.value.invoke(KEY, state)
 
         // Listener is notified of updated state
         verify(listener)
@@ -1332,11 +1502,10 @@
     @Test
     fun testPlaybackStateChange_keyDoesNotExist_doesNothing() {
         val state = PlaybackState.Builder().build()
-        verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
 
         // No media added with this key
 
-        callbackCaptor.value.invoke(KEY, state)
+        stateCallbackCaptor.value.invoke(KEY, state)
         verify(listener, never())
             .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
@@ -1352,10 +1521,9 @@
 
         // And then get a state update
         val state = PlaybackState.Builder().build()
-        verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
 
         // Then no changes are made
-        callbackCaptor.value.invoke(KEY, state)
+        stateCallbackCaptor.value.invoke(KEY, state)
         verify(listener, never())
             .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
     }
@@ -1367,8 +1535,7 @@
         whenever(controller.playbackState).thenReturn(state)
 
         addNotificationAndLoad()
-        verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
-        callbackCaptor.value.invoke(KEY, state)
+        stateCallbackCaptor.value.invoke(KEY, state)
 
         verify(listener)
             .onMediaDataLoaded(
@@ -1410,8 +1577,7 @@
         backgroundExecutor.runAllReady()
         foregroundExecutor.runAllReady()
 
-        verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
-        callbackCaptor.value.invoke(PACKAGE_NAME, state)
+        stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
 
         verify(listener)
             .onMediaDataLoaded(
@@ -1436,8 +1602,7 @@
                 .build()
 
         addNotificationAndLoad()
-        verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor)
-        callbackCaptor.value.invoke(KEY, state)
+        stateCallbackCaptor.value.invoke(KEY, state)
 
         verify(listener)
             .onMediaDataLoaded(
@@ -1485,6 +1650,177 @@
         assertThat(mediaDataCaptor.value.isClearable).isFalse()
     }
 
+    @Test
+    fun testRetain_notifPlayer_notifRemoved_setToResume() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+
+        // When a media control based on notification is added, times out, and then removed
+        addNotificationAndLoad()
+        mediaDataManager.setTimedOut(KEY, timedOut = true)
+        assertThat(mediaDataCaptor.value.active).isFalse()
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // It is converted to a resume player
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        assertThat(mediaDataCaptor.value.active).isFalse()
+        verify(logger)
+            .logActiveConvertedToResume(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId)
+            )
+    }
+
+    @Test
+    fun testRetain_notifPlayer_sessionDestroyed_doesNotChange() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+
+        // When a media control based on notification is added and times out
+        addNotificationAndLoad()
+        mediaDataManager.setTimedOut(KEY, timedOut = true)
+        assertThat(mediaDataCaptor.value.active).isFalse()
+
+        // and then the session is destroyed
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It remains as a regular player
+        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never())
+            .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+    }
+
+    @Test
+    fun testRetain_notifPlayer_removeWhileActive_fullyRemoved() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+
+        // When a media control based on notification is added and then removed, without timing out
+        addNotificationAndLoad()
+        val data = mediaDataCaptor.value
+        assertThat(data.active).isTrue()
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // It is fully removed
+        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+        verify(listener, never())
+            .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+    }
+
+    @Test
+    fun testRetain_canResume_removeWhileActive_setToResume() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+
+        // When a media control that supports resumption is added
+        addNotificationAndLoad()
+        val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {})
+        mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable)
+
+        // And then removed while still active
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // It is converted to a resume player
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        assertThat(mediaDataCaptor.value.active).isFalse()
+        verify(logger)
+            .logActiveConvertedToResume(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId)
+            )
+    }
+
+    @Test
+    fun testRetain_sessionPlayer_notifRemoved_doesNotChange() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control with PlaybackState actions is added, times out,
+        // and then the notification is removed
+        addNotificationAndLoad()
+        val data = mediaDataCaptor.value
+        assertThat(data.active).isTrue()
+        mediaDataManager.setTimedOut(KEY, timedOut = true)
+        mediaDataManager.onNotificationRemoved(KEY)
+
+        // It remains as a regular player
+        verify(listener, never()).onMediaDataRemoved(eq(KEY))
+        verify(listener, never())
+            .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+    }
+
+    @Test
+    fun testRetain_sessionPlayer_sessionDestroyed_setToResume() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control with PlaybackState actions is added, times out,
+        // and then the session is destroyed
+        addNotificationAndLoad()
+        val data = mediaDataCaptor.value
+        assertThat(data.active).isTrue()
+        mediaDataManager.setTimedOut(KEY, timedOut = true)
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is converted to a resume player
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        assertThat(mediaDataCaptor.value.active).isFalse()
+        verify(logger)
+            .logActiveConvertedToResume(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId)
+            )
+    }
+
+    @Test
+    fun testRetain_sessionPlayer_destroyedWhileActive_fullyRemoved() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control using session actions is added, and then the session is destroyed
+        // without timing out first
+        addNotificationAndLoad()
+        val data = mediaDataCaptor.value
+        assertThat(data.active).isTrue()
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is fully removed
+        verify(listener).onMediaDataRemoved(eq(KEY))
+        verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+        verify(listener, never())
+            .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+    }
+
     /** Helper function to add a media notification and capture the resulting MediaData */
     private fun addNotificationAndLoad() {
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
@@ -1500,4 +1836,37 @@
                 eq(false)
             )
     }
+
+    /** Helper function to set up a PlaybackState with action */
+    private fun addPlaybackStateAction() {
+        val stateActions = PlaybackState.ACTION_PLAY_PAUSE
+        val stateBuilder = PlaybackState.Builder().setActions(stateActions)
+        stateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 1.0f)
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+    }
+
+    /** Helper function to add a resumption control and capture the resulting MediaData */
+    private fun addResumeControlAndLoad(desc: MediaDescription) {
+        mediaDataManager.addResumptionControls(
+            USER_ID,
+            desc,
+            Runnable {},
+            session.sessionToken,
+            APP_NAME,
+            pendingIntent,
+            PACKAGE_NAME
+        )
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(null),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
index 344dffa..8baa06a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
@@ -25,13 +25,16 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
 import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -48,7 +51,6 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 
 private const val KEY = "KEY"
@@ -56,6 +58,7 @@
 private const val SESSION_KEY = "SESSION_KEY"
 private const val SESSION_ARTIST = "SESSION_ARTIST"
 private const val SESSION_TITLE = "SESSION_TITLE"
+private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
 
 private fun <T> anyObject(): T {
     return Mockito.anyObject<T>()
@@ -72,6 +75,7 @@
     private lateinit var executor: FakeExecutor
     @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
     @Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit
+    @Mock private lateinit var sessionCallback: (String) -> Unit
     @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
     @Captor
     private lateinit var dozingCallbackCaptor:
@@ -84,10 +88,13 @@
     private lateinit var resumeData: MediaData
     private lateinit var mediaTimeoutListener: MediaTimeoutListener
     private var clock = FakeSystemClock()
+    @Mock private lateinit var mediaFlags: MediaFlags
+    @Mock private lateinit var smartspaceData: SmartspaceMediaData
 
     @Before
     fun setup() {
-        `when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
+        whenever(mediaControllerFactory.create(any())).thenReturn(mediaController)
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         executor = FakeExecutor(clock)
         mediaTimeoutListener =
             MediaTimeoutListener(
@@ -95,10 +102,12 @@
                 executor,
                 logger,
                 statusBarStateController,
-                clock
+                clock,
+                mediaFlags,
             )
         mediaTimeoutListener.timeoutCallback = timeoutCallback
         mediaTimeoutListener.stateCallback = stateCallback
+        mediaTimeoutListener.sessionCallback = sessionCallback
 
         // Create a media session and notification for testing.
         metadataBuilder =
@@ -131,9 +140,9 @@
     @Test
     fun testOnMediaDataLoaded_registersPlaybackListener() {
         val playingState = mock(android.media.session.PlaybackState::class.java)
-        `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+        whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
 
-        `when`(mediaController.playbackState).thenReturn(playingState)
+        whenever(mediaController.playbackState).thenReturn(playingState)
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
         verify(logger).logPlaybackState(eq(KEY), eq(playingState))
@@ -186,8 +195,8 @@
 
         // To playing
         val playingState = mock(android.media.session.PlaybackState::class.java)
-        `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
-        `when`(mediaController.playbackState).thenReturn(playingState)
+        whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+        whenever(mediaController.playbackState).thenReturn(playingState)
         mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
         verify(mediaController).unregisterCallback(anyObject())
         verify(mediaController).registerCallback(anyObject())
@@ -206,8 +215,8 @@
 
         // Migrate, still not playing
         val playingState = mock(android.media.session.PlaybackState::class.java)
-        `when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
-        `when`(mediaController.playbackState).thenReturn(playingState)
+        whenever(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
+        whenever(mediaController.playbackState).thenReturn(playingState)
         mediaTimeoutListener.onMediaDataLoaded(newKey, KEY, mediaData)
 
         // The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor
@@ -284,6 +293,7 @@
         verify(mediaController).unregisterCallback(anyObject())
         assertThat(executor.numPending()).isEqualTo(0)
         verify(logger).logSessionDestroyed(eq(KEY))
+        verify(sessionCallback).invoke(eq(KEY))
     }
 
     @Test
@@ -293,8 +303,8 @@
 
         // WHEN we get an update with media playing
         val playingState = mock(android.media.session.PlaybackState::class.java)
-        `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
-        `when`(mediaController.playbackState).thenReturn(playingState)
+        whenever(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+        whenever(mediaController.playbackState).thenReturn(playingState)
         val mediaPlaying = mediaData.copy(isPlaying = true)
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaPlaying)
 
@@ -322,6 +332,7 @@
         // THEN the controller is unregistered, but the timeout is still scheduled
         verify(mediaController).unregisterCallback(anyObject())
         assertThat(executor.numPending()).isEqualTo(1)
+        verify(sessionCallback, never()).invoke(eq(KEY))
     }
 
     @Test
@@ -343,7 +354,7 @@
         // WHEN regular media is paused
         val pausedState =
             PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
-        `when`(mediaController.playbackState).thenReturn(pausedState)
+        whenever(mediaController.playbackState).thenReturn(pausedState)
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         assertThat(executor.numPending()).isEqualTo(1)
 
@@ -375,7 +386,7 @@
         // AND that media is resumed
         val playingState =
             PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
-        `when`(mediaController.playbackState).thenReturn(playingState)
+        whenever(mediaController.playbackState).thenReturn(playingState)
         mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
 
         // THEN the timeout length is changed to a regular media control
@@ -589,8 +600,91 @@
         assertThat(executor.numPending()).isEqualTo(1)
     }
 
+    @Test
+    fun testSmartspaceDataLoaded_schedulesTimeout() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        val duration = 60_000
+        val createTime = 1234L
+        val expireTime = createTime + duration
+        whenever(smartspaceData.headphoneConnectionTimeMillis).thenReturn(createTime)
+        whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime)
+
+        mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+        assertThat(executor.numPending()).isEqualTo(1)
+        assertThat(executor.advanceClockToNext()).isEqualTo(duration)
+    }
+
+    @Test
+    fun testSmartspaceMediaData_timesOut_invokesCallback() {
+        // Given a pending timeout
+        testSmartspaceDataLoaded_schedulesTimeout()
+
+        executor.runAllReady()
+        verify(timeoutCallback).invoke(eq(SMARTSPACE_KEY), eq(true))
+    }
+
+    @Test
+    fun testSmartspaceDataLoaded_alreadyExists_updatesTimeout() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        val duration = 100
+        val createTime = 1234L
+        val expireTime = createTime + duration
+        whenever(smartspaceData.headphoneConnectionTimeMillis).thenReturn(createTime)
+        whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime)
+
+        mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+        assertThat(executor.numPending()).isEqualTo(1)
+
+        val expiryLonger = expireTime + duration
+        whenever(smartspaceData.expiryTimeMs).thenReturn(expiryLonger)
+        mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+        assertThat(executor.numPending()).isEqualTo(1)
+        assertThat(executor.advanceClockToNext()).isEqualTo(duration * 2)
+    }
+
+    @Test
+    fun testSmartspaceDataRemoved_cancelTimeout() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+        mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+        assertThat(executor.numPending()).isEqualTo(1)
+
+        mediaTimeoutListener.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+        assertThat(executor.numPending()).isEqualTo(0)
+    }
+
+    @Test
+    fun testSmartspaceData_dozedPastTimeout_invokedOnWakeup() {
+        // Given a pending timeout
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+        verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
+        val duration = 60_000
+        val createTime = 1234L
+        val expireTime = createTime + duration
+        whenever(smartspaceData.headphoneConnectionTimeMillis).thenReturn(createTime)
+        whenever(smartspaceData.expiryTimeMs).thenReturn(expireTime)
+
+        mediaTimeoutListener.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+        assertThat(executor.numPending()).isEqualTo(1)
+
+        // And we doze past the scheduled timeout
+        val time = clock.currentTimeMillis()
+        clock.setElapsedRealtime(time + duration * 2)
+        assertThat(executor.numPending()).isEqualTo(1)
+
+        // Then when no longer dozing, the timeout runs immediately
+        dozingCallbackCaptor.value.onDozingChanged(false)
+        verify(timeoutCallback).invoke(eq(SMARTSPACE_KEY), eq(true))
+        verify(logger).logTimeout(eq(SMARTSPACE_KEY))
+
+        // and cancel any later scheduled timeout
+        assertThat(executor.numPending()).isEqualTo(0)
+    }
+
     private fun loadMediaDataWithPlaybackState(state: PlaybackState) {
-        `when`(mediaController.playbackState).thenReturn(state)
+        whenever(mediaController.playbackState).thenReturn(state)
         mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
         verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index e4e95e5..e201b6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -46,6 +47,7 @@
 import com.android.systemui.util.time.FakeSystemClock
 import javax.inject.Provider
 import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import org.junit.Before
 import org.junit.Ignore
@@ -87,6 +89,7 @@
     @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
     @Mock lateinit var mediaCarousel: MediaScrollView
     @Mock lateinit var pageIndicator: PageIndicator
+    @Mock lateinit var mediaFlags: MediaFlags
     @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
     @Captor
     lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
@@ -114,7 +117,8 @@
                 falsingManager,
                 dumpManager,
                 logger,
-                debugLogger
+                debugLogger,
+                mediaFlags,
             )
         verify(configurationController).addCallback(capture(configListener))
         verify(mediaDataManager).addListener(capture(listener))
@@ -123,6 +127,7 @@
         whenever(mediaControlPanelFactory.get()).thenReturn(panel)
         whenever(panel.mediaViewController).thenReturn(mediaViewController)
         whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
         MediaPlayerData.clear()
     }
 
@@ -700,4 +705,39 @@
             mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
         )
     }
+
+    @Test
+    fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() {
+        testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded()
+
+        // When an update to existing smartspace data is loaded
+        listener.value.onSmartspaceMediaDataLoaded(
+            SMARTSPACE_KEY,
+            EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
+            true
+        )
+
+        // Then the carousel is updated
+        assertTrue(MediaPlayerData.playerKeys().elementAt(0).data.active)
+        assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
+    }
+
+    @Test
+    fun testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() {
+        whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
+
+        // When inactive smartspace data is loaded
+        listener.value.onSmartspaceMediaDataLoaded(
+            SMARTSPACE_KEY,
+            EMPTY_SMARTSPACE_MEDIA_DATA,
+            false
+        )
+
+        // Then it is added to the carousel with correct state
+        assertTrue(MediaPlayerData.playerKeys().elementAt(0).isSsMediaRec)
+        assertFalse(MediaPlayerData.playerKeys().elementAt(0).data.active)
+
+        assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).isSsMediaRec)
+        assertFalse(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index b35dd26..55a33b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -52,6 +52,7 @@
 import androidx.constraintlayout.widget.Barrier
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.LiveData
+import androidx.media.utils.MediaConstants
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
 import com.android.internal.widget.CachingIconView
@@ -204,6 +205,15 @@
     @Mock private lateinit var coverContainer1: ViewGroup
     @Mock private lateinit var coverContainer2: ViewGroup
     @Mock private lateinit var coverContainer3: ViewGroup
+    @Mock private lateinit var recAppIconItem: CachingIconView
+    @Mock private lateinit var recCardTitle: TextView
+    @Mock private lateinit var recProgressBar1: SeekBar
+    @Mock private lateinit var recProgressBar2: SeekBar
+    @Mock private lateinit var recProgressBar3: SeekBar
+    @Mock private lateinit var recSubtitleMock1: TextView
+    @Mock private lateinit var recSubtitleMock2: TextView
+    @Mock private lateinit var recSubtitleMock3: TextView
+    @Mock private lateinit var coverItem: ImageView
     private lateinit var coverItem1: ImageView
     private lateinit var coverItem2: ImageView
     private lateinit var coverItem3: ImageView
@@ -220,6 +230,7 @@
             this.set(Flags.UMO_TURBULENCE_NOISE, false)
             this.set(Flags.MEDIA_FALSING_PENALTY, true)
             this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
+            this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
         }
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -902,6 +913,17 @@
     }
 
     @Test
+    fun bind_resumeState_withProgress() {
+        val progress = 0.5
+        val state = mediaData.copy(resumption = true, resumeProgress = progress)
+
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(state, PACKAGE)
+
+        verify(seekBarViewModel).updateStaticProgress(progress)
+    }
+
+    @Test
     fun bindNotificationActions() {
         val icon = context.getDrawable(android.R.drawable.ic_media_play)
         val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
@@ -2059,6 +2081,114 @@
     }
 
     @Test
+    fun bindRecommendation_setAfterExecutors() {
+        fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
+        whenever(recommendationViewHolder.mediaAppIcons)
+            .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+        whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
+        whenever(recommendationViewHolder.mediaCoverItems)
+            .thenReturn(listOf(coverItem, coverItem, coverItem))
+        whenever(recommendationViewHolder.mediaProgressBars)
+            .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+        whenever(recommendationViewHolder.mediaSubtitles)
+            .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
+
+        val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(bmp)
+        canvas.drawColor(Color.RED)
+        val albumArt = Icon.createWithBitmap(bmp)
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "title1")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", "title3")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
+            )
+
+        player.attachRecommendation(recommendationViewHolder)
+        player.bindRecommendation(data)
+        bgExecutor.runAllReady()
+        mainExecutor.runAllReady()
+
+        verify(recCardTitle).setTextColor(any<Int>())
+        verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java))
+        verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java))
+    }
+
+    @Test
+    fun bindRecommendationWithProgressBars() {
+        fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
+        whenever(recommendationViewHolder.mediaAppIcons)
+            .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+        whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
+        whenever(recommendationViewHolder.mediaCoverItems)
+            .thenReturn(listOf(coverItem, coverItem, coverItem))
+        whenever(recommendationViewHolder.mediaProgressBars)
+            .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+        whenever(recommendationViewHolder.mediaSubtitles)
+            .thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
+
+        val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(bmp)
+        canvas.drawColor(Color.RED)
+        val albumArt = Icon.createWithBitmap(bmp)
+        val bundle =
+            Bundle().apply {
+                putInt(
+                    MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
+                    MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
+                )
+                putDouble(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, 0.5)
+            }
+        val data =
+            smartspaceData.copy(
+                recommendations =
+                    listOf(
+                        SmartspaceAction.Builder("id1", "title1")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(bundle)
+                            .build(),
+                        SmartspaceAction.Builder("id2", "title2")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build(),
+                        SmartspaceAction.Builder("id3", "title3")
+                            .setSubtitle("subtitle1")
+                            .setIcon(albumArt)
+                            .setExtras(Bundle.EMPTY)
+                            .build()
+                    )
+            )
+
+        player.attachRecommendation(recommendationViewHolder)
+        player.bindRecommendation(data)
+
+        verify(recProgressBar1).setProgress(50)
+        verify(recProgressBar1).visibility = View.VISIBLE
+        verify(recProgressBar2).visibility = View.GONE
+        verify(recProgressBar3).visibility = View.GONE
+        verify(recSubtitleMock1).visibility = View.GONE
+        verify(recSubtitleMock2).visibility = View.VISIBLE
+        verify(recSubtitleMock3).visibility = View.VISIBLE
+    }
+
+    @Test
     fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
         fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
         val semanticActions =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 4ed6d7c..2f7eac2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.util.animation.MeasurementInput
 import com.android.systemui.util.animation.TransitionLayout
 import com.android.systemui.util.animation.TransitionViewState
@@ -55,6 +56,7 @@
     @Mock private lateinit var mediaTitleWidgetState: WidgetState
     @Mock private lateinit var mediaSubTitleWidgetState: WidgetState
     @Mock private lateinit var mediaContainerWidgetState: WidgetState
+    @Mock private lateinit var mediaFlags: MediaFlags
 
     val delta = 0.1F
 
@@ -64,7 +66,13 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         mediaViewController =
-            MediaViewController(context, configurationController, mediaHostStatesManager, logger)
+            MediaViewController(
+                context,
+                configurationController,
+                mediaHostStatesManager,
+                logger,
+                mediaFlags,
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 7c3c9d2..8fd15c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -29,6 +29,7 @@
 import android.testing.AndroidTestingRunner;
 import android.view.View;
 import android.widget.LinearLayout;
+import android.widget.SeekBar;
 
 import androidx.core.graphics.drawable.IconCompat;
 import androidx.test.filters.SmallTest;
@@ -43,6 +44,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -57,6 +60,7 @@
     private static final String TEST_DEVICE_ID_1 = "test_device_id_1";
     private static final String TEST_DEVICE_ID_2 = "test_device_id_2";
     private static final String TEST_SESSION_NAME = "test_session_name";
+
     private static final int TEST_MAX_VOLUME = 20;
     private static final int TEST_CURRENT_VOLUME = 10;
 
@@ -69,6 +73,8 @@
     private IconCompat mIconCompat = mock(IconCompat.class);
     private View mDialogLaunchView = mock(View.class);
 
+    @Captor
+    private ArgumentCaptor<SeekBar.OnSeekBarChangeListener> mOnSeekBarChangeListenerCaptor;
     private MediaOutputAdapter mMediaOutputAdapter;
     private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder;
     private List<MediaDevice> mMediaDevices = new ArrayList<>();
@@ -78,6 +84,7 @@
     @Before
     public void setUp() {
         when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
+        when(mMediaOutputController.isSubStatusSupported()).thenReturn(false);
         when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems);
         when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
         when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
@@ -348,6 +355,24 @@
     }
 
     @Test
+    public void onBindViewHolder_dragSeekbar_setsVolume() {
+        mOnSeekBarChangeListenerCaptor = ArgumentCaptor.forClass(
+                SeekBar.OnSeekBarChangeListener.class);
+        MediaOutputSeekbar mSpySeekbar = spy(mViewHolder.mSeekBar);
+        mViewHolder.mSeekBar = mSpySeekbar;
+        when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME);
+        when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_MAX_VOLUME);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        verify(mViewHolder.mSeekBar).setOnSeekBarChangeListener(
+                mOnSeekBarChangeListenerCaptor.capture());
+
+        mOnSeekBarChangeListenerCaptor.getValue().onStopTrackingTouch(mViewHolder.mSeekBar);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mMediaOutputController).logInteractionAdjustVolume(mMediaDevice1);
+    }
+
+    @Test
     public void onBindViewHolder_bindSelectableDevice_verifyView() {
         List<MediaDevice> selectableDevices = new ArrayList<>();
         selectableDevices.add(mMediaDevice2);
@@ -404,6 +429,24 @@
     }
 
     @Test
+    public void subStatusSupported_onBindViewHolder_bindFailedStateDevice_verifyView() {
+        String deviceStatus = "";
+        when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
+        when(mMediaDevice2.hasDisabledReason()).thenReturn(true);
+        when(mMediaDevice2.getDisableReason()).thenReturn(-1);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mSubTitleText.getText()).isEqualTo(deviceStatus);
+        assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_2);
+    }
+
+    @Test
     public void onBindViewHolder_inTransferring_bindTransferringDevice_verifyView() {
         when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(true);
         when(mMediaDevice1.getState()).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 094d69a..9a0bd9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -243,6 +243,13 @@
     }
 
     @Test
+    public void dismissDialog_closesDialogByBroadcastSender() {
+        mMediaOutputBaseDialogImpl.dismissDialog();
+
+        verify(mBroadcastSender).closeSystemDialogs();
+    }
+
+    @Test
     public void whenBroadcasting_verifyLeBroadcastServiceCallBackIsRegisteredAndUnregistered() {
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
                 mLocalBluetoothLeBroadcast);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index f5432e2..7c36e46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -93,6 +93,11 @@
     private static final String TEST_SONG = "test_song";
     private static final String TEST_SESSION_ID = "test_session_id";
     private static final String TEST_SESSION_NAME = "test_session_name";
+    private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
+            ActivityLaunchAnimator.Controller.class);
+    private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
+            NearbyMediaDevicesManager.class);
     // Mock
     private MediaController mMediaController = mock(MediaController.class);
     private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
@@ -111,12 +116,7 @@
     private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
     private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
-    private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
     private FeatureFlags mFlags = mock(FeatureFlags.class);
-    private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
-            ActivityLaunchAnimator.Controller.class);
-    private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
-            NearbyMediaDevicesManager.class);
     private View mDialogLaunchView = mock(View.class);
     private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
 
@@ -252,6 +252,13 @@
     }
 
     @Test
+    public void tryToLaunchMediaApplication_nullIntent_skip() {
+        mMediaOutputController.tryToLaunchMediaApplication();
+
+        verify(mCb, never()).dismissDialog();
+    }
+
+    @Test
     public void onDevicesUpdated_unregistersNearbyDevicesCallback() throws RemoteException {
         mMediaOutputController.start(mCb);
 
@@ -515,6 +522,17 @@
     }
 
     @Test
+    public void logInteractionAdjustVolume_triggersFromMetricLogger() {
+        MediaOutputMetricLogger spyMediaOutputMetricLogger = spy(
+                mMediaOutputController.mMetricLogger);
+        mMediaOutputController.mMetricLogger = spyMediaOutputMetricLogger;
+
+        mMediaOutputController.logInteractionAdjustVolume(mMediaDevice1);
+
+        verify(spyMediaOutputMetricLogger).logInteractionAdjustVolume(mMediaDevice1);
+    }
+
+    @Test
     public void getSessionVolumeMax_triggersFromLocalMediaManager() {
         mMediaOutputController.getSessionVolumeMax();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
similarity index 70%
copy from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
copy to packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
index 0e7bf8d..8da1c64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtilsTest.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogcatEchoTracker
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -31,16 +30,15 @@
 import org.mockito.Mockito.mock
 
 @SmallTest
-class MediaTttLoggerTest : SysuiTestCase() {
+class MediaTttLoggerUtilsTest : SysuiTestCase() {
 
     private lateinit var buffer: LogBuffer
-    private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
 
     @Before
-    fun setUp () {
-        buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
-            .create("buffer", 10)
-        logger = MediaTttLogger(DEVICE_TYPE_TAG, buffer)
+    fun setUp() {
+        buffer =
+            LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+                .create("buffer", 10)
     }
 
     @Test
@@ -49,35 +47,33 @@
         val id = "test id"
         val packageName = "this.is.a.package"
 
-        logger.logStateChange(stateName, id, packageName)
+        MediaTttLoggerUtils.logStateChange(buffer, TAG, stateName, id, packageName)
 
         val actualString = getStringFromBuffer()
-        assertThat(actualString).contains(DEVICE_TYPE_TAG)
+        assertThat(actualString).contains(TAG)
         assertThat(actualString).contains(stateName)
         assertThat(actualString).contains(id)
         assertThat(actualString).contains(packageName)
     }
 
     @Test
-    fun logPackageNotFound_bufferHasPackageName() {
-        val packageName = "this.is.a.package"
-
-        logger.logPackageNotFound(packageName)
+    fun logStateChangeError_hasState() {
+        MediaTttLoggerUtils.logStateChangeError(buffer, TAG, 3456)
 
         val actualString = getStringFromBuffer()
-        assertThat(actualString).contains(packageName)
+        assertThat(actualString).contains(TAG)
+        assertThat(actualString).contains("3456")
     }
 
     @Test
-    fun logRemovalBypass_bufferHasReasons() {
-        val removalReason = "fakeRemovalReason"
-        val bypassReason = "fakeBypassReason"
+    fun logPackageNotFound_bufferHasPackageName() {
+        val packageName = "this.is.a.package"
 
-        logger.logRemovalBypass(removalReason, bypassReason)
+        MediaTttLoggerUtils.logPackageNotFound(buffer, TAG, packageName)
 
         val actualString = getStringFromBuffer()
-        assertThat(actualString).contains(removalReason)
-        assertThat(actualString).contains(bypassReason)
+        assertThat(actualString).contains(TAG)
+        assertThat(actualString).contains(packageName)
     }
 
     private fun getStringFromBuffer(): String {
@@ -87,4 +83,4 @@
     }
 }
 
-private const val DEVICE_TYPE_TAG = "TEST TYPE"
+private const val TAG = "TEST TAG"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 561867f..8055b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -41,7 +40,6 @@
     private lateinit var appIconFromPackageName: Drawable
     @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var applicationInfo: ApplicationInfo
-    @Mock private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
 
     @Before
     fun setUp() {
@@ -67,8 +65,7 @@
 
     @Test
     fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
-        val iconInfo =
-            MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger)
+        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null) {}
 
         assertThat(iconInfo.isAppIcon).isFalse()
         assertThat(iconInfo.contentDescription.loadContentDescription(context))
@@ -77,8 +74,19 @@
     }
 
     @Test
+    fun getIconInfoFromPackageName_nullPackageName_exceptionFnNotTriggered() {
+        var exceptionTriggered = false
+
+        MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null) {
+            exceptionTriggered = true
+        }
+
+        assertThat(exceptionTriggered).isFalse()
+    }
+
+    @Test
     fun getIconInfoFromPackageName_invalidPackageName_returnsDefault() {
-        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger)
+        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName") {}
 
         assertThat(iconInfo.isAppIcon).isFalse()
         assertThat(iconInfo.contentDescription.loadContentDescription(context))
@@ -87,8 +95,19 @@
     }
 
     @Test
+    fun getIconInfoFromPackageName_invalidPackageName_exceptionFnTriggered() {
+        var exceptionTriggered = false
+
+        MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = "fakePackageName") {
+            exceptionTriggered = true
+        }
+
+        assertThat(exceptionTriggered).isTrue()
+    }
+
+    @Test
     fun getIconInfoFromPackageName_validPackageName_returnsAppInfo() {
-        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger)
+        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME) {}
 
         assertThat(iconInfo.isAppIcon).isTrue()
         assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Loaded(appIconFromPackageName))
@@ -96,6 +115,17 @@
     }
 
     @Test
+    fun getIconInfoFromPackageName_validPackageName_exceptionFnNotTriggered() {
+        var exceptionTriggered = false
+
+        MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME) {
+            exceptionTriggered = true
+        }
+
+        assertThat(exceptionTriggered).isFalse()
+    }
+
+    @Test
     fun iconInfo_toTintedIcon_loaded() {
         val contentDescription = ContentDescription.Loaded("test")
         val drawable = context.getDrawable(R.drawable.ic_cake)!!
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index 9c4e849..bd042c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -24,7 +24,6 @@
 import android.view.accessibility.AccessibilityManager
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -35,7 +34,7 @@
 class FakeMediaTttChipControllerReceiver(
     commandQueue: CommandQueue,
     context: Context,
-    logger: MediaTttLogger<ChipReceiverInfo>,
+    logger: MediaTttReceiverLogger,
     windowManager: WindowManager,
     mainExecutor: DelayableExecutor,
     accessibilityManager: AccessibilityManager,
@@ -48,6 +47,7 @@
     viewUtil: ViewUtil,
     wakeLockBuilder: WakeLock.Builder,
     systemClock: SystemClock,
+    rippleController: MediaTttReceiverRippleController,
 ) :
     MediaTttChipControllerReceiver(
         commandQueue,
@@ -65,6 +65,7 @@
         viewUtil,
         wakeLockBuilder,
         systemClock,
+        rippleController,
     ) {
     override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
         // Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index cefc742..dba2da7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -68,7 +67,7 @@
     @Mock
     private lateinit var applicationInfo: ApplicationInfo
     @Mock
-    private lateinit var logger: MediaTttLogger<ChipReceiverInfo>
+    private lateinit var logger: MediaTttReceiverLogger
     @Mock
     private lateinit var accessibilityManager: AccessibilityManager
     @Mock
@@ -85,6 +84,8 @@
     private lateinit var windowManager: WindowManager
     @Mock
     private lateinit var commandQueue: CommandQueue
+    @Mock
+    private lateinit var rippleController: MediaTttReceiverRippleController
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
     private lateinit var uiEventLoggerFake: UiEventLoggerFake
@@ -134,6 +135,7 @@
             viewUtil,
             fakeWakeLockBuilder,
             fakeClock,
+            rippleController,
         )
         controllerReceiver.start()
 
@@ -163,6 +165,7 @@
             viewUtil,
             fakeWakeLockBuilder,
             fakeClock,
+            rippleController,
         )
         controllerReceiver.start()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
similarity index 71%
rename from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
index 0e7bf8d..95df484 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverLoggerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.taptotransfer.common
+package com.android.systemui.media.taptotransfer.receiver
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -22,7 +22,6 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogcatEchoTracker
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -31,16 +30,17 @@
 import org.mockito.Mockito.mock
 
 @SmallTest
-class MediaTttLoggerTest : SysuiTestCase() {
+class MediaTttReceiverLoggerTest : SysuiTestCase() {
 
     private lateinit var buffer: LogBuffer
-    private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
+    private lateinit var logger: MediaTttReceiverLogger
 
     @Before
-    fun setUp () {
-        buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
-            .create("buffer", 10)
-        logger = MediaTttLogger(DEVICE_TYPE_TAG, buffer)
+    fun setUp() {
+        buffer =
+            LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+                .create("buffer", 10)
+        logger = MediaTttReceiverLogger(buffer)
     }
 
     @Test
@@ -52,13 +52,20 @@
         logger.logStateChange(stateName, id, packageName)
 
         val actualString = getStringFromBuffer()
-        assertThat(actualString).contains(DEVICE_TYPE_TAG)
         assertThat(actualString).contains(stateName)
         assertThat(actualString).contains(id)
         assertThat(actualString).contains(packageName)
     }
 
     @Test
+    fun logStateChangeError_hasState() {
+        logger.logStateChangeError(3456)
+
+        val actualString = getStringFromBuffer()
+        assertThat(actualString).contains("3456")
+    }
+
+    @Test
     fun logPackageNotFound_bufferHasPackageName() {
         val packageName = "this.is.a.package"
 
@@ -68,23 +75,9 @@
         assertThat(actualString).contains(packageName)
     }
 
-    @Test
-    fun logRemovalBypass_bufferHasReasons() {
-        val removalReason = "fakeRemovalReason"
-        val bypassReason = "fakeBypassReason"
-
-        logger.logRemovalBypass(removalReason, bypassReason)
-
-        val actualString = getStringFromBuffer()
-        assertThat(actualString).contains(removalReason)
-        assertThat(actualString).contains(bypassReason)
-    }
-
     private fun getStringFromBuffer(): String {
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
         return stringWriter.toString()
     }
 }
-
-private const val DEVICE_TYPE_TAG = "TEST TYPE"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index f5b3959..db890f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -40,18 +40,21 @@
 import com.android.systemui.common.shared.model.Text.Companion.loadText
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
+import com.android.systemui.temporarydisplay.chipbar.ChipbarAnimator
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
-import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
 import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
-import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
 import com.android.systemui.util.wakelock.WakeLockFake
@@ -61,6 +64,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
+import org.mockito.Mockito.atLeast
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
@@ -86,13 +90,14 @@
     @Mock private lateinit var falsingManager: FalsingManager
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var chipbarLogger: ChipbarLogger
-    @Mock private lateinit var logger: MediaTttLogger<ChipbarInfo>
+    @Mock private lateinit var logger: MediaTttSenderLogger
     @Mock private lateinit var mediaTttFlags: MediaTttFlags
     @Mock private lateinit var packageManager: PackageManager
     @Mock private lateinit var powerManager: PowerManager
     @Mock private lateinit var viewUtil: ViewUtil
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var vibratorHelper: VibratorHelper
+    @Mock private lateinit var swipeHandler: SwipeChipbarAwayGestureHandler
     private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
     private lateinit var fakeWakeLock: WakeLockFake
     private lateinit var chipbarCoordinator: ChipbarCoordinator
@@ -132,7 +137,7 @@
         uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
 
         chipbarCoordinator =
-            FakeChipbarCoordinator(
+            ChipbarCoordinator(
                 context,
                 chipbarLogger,
                 windowManager,
@@ -141,8 +146,10 @@
                 configurationController,
                 dumpManager,
                 powerManager,
+                ChipbarAnimator(),
                 falsingManager,
                 falsingCollector,
+                swipeHandler,
                 viewUtil,
                 vibratorHelper,
                 fakeWakeLockBuilder,
@@ -155,15 +162,14 @@
                 chipbarCoordinator,
                 commandQueue,
                 context,
+                dumpManager,
                 logger,
                 mediaTttFlags,
                 uiEventLogger,
             )
         underTest.start()
 
-        val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
-        verify(commandQueue).addCallback(callbackCaptor.capture())
-        commandQueueCallback = callbackCaptor.value!!
+        setCommandQueueCallback()
     }
 
     @Test
@@ -175,6 +181,7 @@
                 chipbarCoordinator,
                 commandQueue,
                 context,
+                dumpManager,
                 logger,
                 mediaTttFlags,
                 uiEventLogger,
@@ -536,6 +543,7 @@
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
         verify(windowManager).addView(viewCaptor.capture(), any())
         verify(windowManager).removeView(viewCaptor.value)
+        verify(logger).logStateMapRemoval(eq(DEFAULT_ID), any())
     }
 
     @Test
@@ -616,7 +624,7 @@
     }
 
     @Test
-    fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
+    fun commandQueueCallback_receiverSucceededThenThisDeviceSucceeded_invalidTransitionLogged() {
         displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
@@ -626,7 +634,7 @@
         reset(windowManager)
 
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
             null
         )
@@ -636,7 +644,7 @@
     }
 
     @Test
-    fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
+    fun commandQueueCallback_thisDeviceSucceededThenReceiverSucceeded_invalidTransitionLogged() {
         displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
@@ -646,7 +654,7 @@
         reset(windowManager)
 
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
-            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
             null
         )
@@ -735,6 +743,99 @@
         verify(windowManager, never()).addView(any(), any())
     }
 
+    /** Regression test for b/266217596. */
+    @Test
+    fun toReceiver_triggeredThenFar_thenSucceeded_updatesToSucceeded() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null,
+        )
+
+        // WHEN a FAR command comes in
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null,
+        )
+
+        // THEN it is ignored and the chipbar is stilled displayed
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+        verify(windowManager, never()).removeView(any())
+
+        // WHEN a SUCCEEDED command comes in
+        val succeededRouteInfo =
+            MediaRoute2Info.Builder(DEFAULT_ID, "Tablet Succeeded")
+                .addFeature("feature")
+                .setClientPackageName(PACKAGE_NAME)
+                .build()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            succeededRouteInfo,
+            /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+                override fun onUndoTriggered() {}
+            },
+        )
+
+        // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded
+        // state. (The "invalid transition" would be FAR => SUCCEEDED.)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(
+                ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText(
+                    "Tablet Succeeded"
+                )
+            )
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+    }
+
+    /** Regression test for b/266217596. */
+    @Test
+    fun toThisDevice_triggeredThenFar_thenSucceeded_updatesToSucceeded() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null,
+        )
+
+        // WHEN a FAR command comes in
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null,
+        )
+
+        // THEN it is ignored and the chipbar is stilled displayed
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+        verify(windowManager, never()).removeView(any())
+
+        // WHEN a SUCCEEDED command comes in
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() {
+                override fun onUndoTriggered() {}
+            },
+        )
+
+        // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded
+        // state. (The "invalid transition" would be FAR => SUCCEEDED.)
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(
+                ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText(
+                    "Tablet Succeeded"
+                )
+            )
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
+        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+    }
+
     @Test
     fun receivesNewStateFromCommandQueue_isLogged() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
@@ -920,6 +1021,341 @@
         verify(windowManager).removeView(any())
     }
 
+    @Test
+    fun newState_viewListenerRegistered() {
+        val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+        underTest =
+            MediaTttSenderCoordinator(
+                mockChipbarCoordinator,
+                commandQueue,
+                context,
+                dumpManager,
+                logger,
+                mediaTttFlags,
+                uiEventLogger,
+            )
+        underTest.start()
+        // Re-set the command queue callback since we've created a new [MediaTttSenderCoordinator]
+        // with a new callback.
+        setCommandQueueCallback()
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null,
+        )
+
+        verify(mockChipbarCoordinator).registerListener(any())
+    }
+
+    @Test
+    fun onInfoPermanentlyRemoved_viewListenerUnregistered() {
+        val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+        underTest =
+            MediaTttSenderCoordinator(
+                mockChipbarCoordinator,
+                commandQueue,
+                context,
+                dumpManager,
+                logger,
+                mediaTttFlags,
+                uiEventLogger,
+            )
+        underTest.start()
+        setCommandQueueCallback()
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null,
+        )
+
+        val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
+        verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
+
+        // WHEN the listener is notified that the view has been removed
+        listenerCaptor.value.onInfoPermanentlyRemoved(DEFAULT_ID, "reason")
+
+        // THEN the media coordinator unregisters the listener
+        verify(logger).logStateMapRemoval(DEFAULT_ID, "reason")
+        verify(mockChipbarCoordinator).unregisterListener(listenerCaptor.value)
+    }
+
+    @Test
+    fun onInfoPermanentlyRemoved_wrongId_viewListenerNotUnregistered() {
+        val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+        underTest =
+            MediaTttSenderCoordinator(
+                mockChipbarCoordinator,
+                commandQueue,
+                context,
+                dumpManager,
+                logger,
+                mediaTttFlags,
+                uiEventLogger,
+            )
+        underTest.start()
+        setCommandQueueCallback()
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null,
+        )
+
+        val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
+        verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
+
+        // WHEN the listener is notified that a different view has been removed
+        listenerCaptor.value.onInfoPermanentlyRemoved("differentViewId", "reason")
+
+        // THEN the media coordinator doesn't unregister the listener
+        verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value)
+    }
+
+    @Test
+    fun farFromReceiverState_viewListenerUnregistered() {
+        val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+        underTest =
+            MediaTttSenderCoordinator(
+                mockChipbarCoordinator,
+                commandQueue,
+                context,
+                dumpManager,
+                logger,
+                mediaTttFlags,
+                uiEventLogger,
+            )
+        underTest.start()
+        setCommandQueueCallback()
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null,
+        )
+
+        val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
+        verify(mockChipbarCoordinator).registerListener(capture(listenerCaptor))
+
+        // WHEN we go to the FAR_FROM_RECEIVER state
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            routeInfo,
+            null
+        )
+
+        // THEN the media coordinator unregisters the listener
+        verify(mockChipbarCoordinator).unregisterListener(listenerCaptor.value)
+    }
+
+    @Test
+    fun statesWithDifferentIds_onInfoPermanentlyRemovedForOneId_viewListenerNotUnregistered() {
+        val mockChipbarCoordinator = mock<ChipbarCoordinator>()
+        underTest =
+            MediaTttSenderCoordinator(
+                mockChipbarCoordinator,
+                commandQueue,
+                context,
+                dumpManager,
+                logger,
+                mediaTttFlags,
+                uiEventLogger,
+            )
+        underTest.start()
+        setCommandQueueCallback()
+
+        // WHEN there are two different media transfers with different IDs
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            MediaRoute2Info.Builder("route1", OTHER_DEVICE_NAME)
+                .addFeature("feature")
+                .setClientPackageName(PACKAGE_NAME)
+                .build(),
+            null,
+        )
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            MediaRoute2Info.Builder("route2", OTHER_DEVICE_NAME)
+                .addFeature("feature")
+                .setClientPackageName(PACKAGE_NAME)
+                .build(),
+            null,
+        )
+
+        val listenerCaptor = argumentCaptor<TemporaryViewDisplayController.Listener>()
+        verify(mockChipbarCoordinator, atLeast(1)).registerListener(capture(listenerCaptor))
+
+        // THEN one of them is removed
+        listenerCaptor.value.onInfoPermanentlyRemoved("route1", "reason")
+
+        // THEN the media coordinator doesn't unregister the listener (since route2 is still active)
+        verify(mockChipbarCoordinator, never()).unregisterListener(listenerCaptor.value)
+        verify(logger).logStateMapRemoval("route1", "reason")
+    }
+
+    /** Regression test for b/266218672. */
+    @Test
+    fun twoIdsDisplayed_oldIdIsFar_viewStillDisplayed() {
+        // WHEN there are two different media transfers with different IDs
+        val route1 =
+            MediaRoute2Info.Builder("route1", OTHER_DEVICE_NAME)
+                .addFeature("feature")
+                .setClientPackageName(PACKAGE_NAME)
+                .build()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            route1,
+            null,
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            MediaRoute2Info.Builder("route2", "Route 2 name")
+                .addFeature("feature")
+                .setClientPackageName(PACKAGE_NAME)
+                .build(),
+            null,
+        )
+        val newView = getChipbarView()
+
+        // WHEN there's a FAR event for the earlier one
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+            route1,
+            null,
+        )
+
+        // THEN it's ignored and the more recent one is still displayed
+        assertThat(newView.getChipText())
+            .isEqualTo(
+                ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText("Route 2 name")
+            )
+    }
+
+    /** Regression test for b/266218672. */
+    @Test
+    fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToEnd() {
+        displayReceiverTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null,
+        )
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+        verify(windowManager).removeView(any())
+
+        reset(windowManager)
+
+        // WHEN we try to show ALMOST_CLOSE_TO_END
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null,
+        )
+
+        // THEN it succeeds
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText())
+    }
+
+    /** Regression test for b/266218672. */
+    @Test
+    fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayReceiverTriggered() {
+        displayReceiverTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null,
+        )
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+        verify(windowManager).removeView(any())
+
+        reset(windowManager)
+
+        // WHEN we try to show RECEIVER_TRIGGERED
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null,
+        )
+
+        // THEN it succeeds
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText())
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+    }
+
+    /** Regression test for b/266218672. */
+    @Test
+    fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToStart() {
+        displayThisDeviceTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null,
+        )
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+        verify(windowManager).removeView(any())
+
+        reset(windowManager)
+
+        // WHEN we try to show ALMOST_CLOSE_TO_START
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null,
+        )
+
+        // THEN it succeeds
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText())
+    }
+
+    /** Regression test for b/266218672. */
+    @Test
+    fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayThisDeviceTriggered() {
+        displayThisDeviceTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null,
+        )
+
+        fakeClock.advanceTime(TIMEOUT + 1L)
+        verify(windowManager).removeView(any())
+
+        reset(windowManager)
+
+        // WHEN we try to show THIS_DEVICE_TRIGGERED
+        val newRouteInfo =
+            MediaRoute2Info.Builder(DEFAULT_ID, "New Name")
+                .addFeature("feature")
+                .setClientPackageName(PACKAGE_NAME)
+                .build()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            newRouteInfo,
+            null,
+        )
+
+        // THEN it succeeds
+        val chipbarView = getChipbarView()
+        assertThat(chipbarView.getChipText())
+            .isEqualTo(
+                ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText("New Name")
+            )
+        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE)
+    }
+
     private fun getChipbarView(): ViewGroup {
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
         verify(windowManager).addView(viewCaptor.capture(), any())
@@ -937,8 +1373,10 @@
 
     private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button)
 
-    private fun ChipStateSender.getExpectedStateText(): String? {
-        return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
+    private fun ChipStateSender.getExpectedStateText(
+        otherDeviceName: String = OTHER_DEVICE_NAME,
+    ): String? {
+        return this.getChipTextString(context, otherDeviceName).loadText(context)
     }
 
     // display receiver triggered state helper method to make sure we start from a valid state
@@ -960,8 +1398,16 @@
             null
         )
     }
+
+    private fun setCommandQueueCallback() {
+        val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
+        verify(commandQueue).addCallback(capture(callbackCaptor))
+        commandQueueCallback = callbackCaptor.value
+        reset(commandQueue)
+    }
 }
 
+private const val DEFAULT_ID = "defaultId"
 private const val APP_NAME = "Fake app name"
 private const val OTHER_DEVICE_NAME = "My Tablet"
 private const val BLANK_DEVICE_NAME = " "
@@ -969,13 +1415,13 @@
 private const val TIMEOUT = 10000
 
 private val routeInfo =
-    MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
+    MediaRoute2Info.Builder(DEFAULT_ID, OTHER_DEVICE_NAME)
         .addFeature("feature")
         .setClientPackageName(PACKAGE_NAME)
         .build()
 
 private val routeInfoWithBlankDeviceName =
-    MediaRoute2Info.Builder("id", BLANK_DEVICE_NAME)
+    MediaRoute2Info.Builder(DEFAULT_ID, BLANK_DEVICE_NAME)
         .addFeature("feature")
         .setClientPackageName(PACKAGE_NAME)
         .build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
similarity index 62%
copy from packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
copy to packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
index 0e7bf8d..0033757 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLoggerTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.media.taptotransfer.common
+package com.android.systemui.media.taptotransfer.sender
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -22,7 +22,6 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogcatEchoTracker
-import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -31,16 +30,17 @@
 import org.mockito.Mockito.mock
 
 @SmallTest
-class MediaTttLoggerTest : SysuiTestCase() {
+class MediaTttSenderLoggerTest : SysuiTestCase() {
 
     private lateinit var buffer: LogBuffer
-    private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
+    private lateinit var logger: MediaTttSenderLogger
 
     @Before
-    fun setUp () {
-        buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
-            .create("buffer", 10)
-        logger = MediaTttLogger(DEVICE_TYPE_TAG, buffer)
+    fun setUp() {
+        buffer =
+            LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java))
+                .create("buffer", 10)
+        logger = MediaTttSenderLogger(buffer)
     }
 
     @Test
@@ -52,13 +52,20 @@
         logger.logStateChange(stateName, id, packageName)
 
         val actualString = getStringFromBuffer()
-        assertThat(actualString).contains(DEVICE_TYPE_TAG)
         assertThat(actualString).contains(stateName)
         assertThat(actualString).contains(id)
         assertThat(actualString).contains(packageName)
     }
 
     @Test
+    fun logStateChangeError_hasState() {
+        logger.logStateChangeError(3456)
+
+        val actualString = getStringFromBuffer()
+        assertThat(actualString).contains("3456")
+    }
+
+    @Test
     fun logPackageNotFound_bufferHasPackageName() {
         val packageName = "this.is.a.package"
 
@@ -80,11 +87,35 @@
         assertThat(actualString).contains(bypassReason)
     }
 
+    @Test
+    fun logStateMap_bufferHasInfo() {
+        val map =
+            mapOf(
+                "123" to ChipStateSender.ALMOST_CLOSE_TO_START_CAST,
+                "456" to ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            )
+
+        logger.logStateMap(map)
+
+        val actualString = getStringFromBuffer()
+        assertThat(actualString).contains("123")
+        assertThat(actualString).contains(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.name)
+        assertThat(actualString).contains("456")
+        assertThat(actualString).contains(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.name)
+    }
+
+    @Test
+    fun logStateMapRemoval_bufferHasInfo() {
+        logger.logStateMapRemoval("456", "testReason")
+
+        val actualString = getStringFromBuffer()
+        assertThat(actualString).contains("456")
+        assertThat(actualString).contains("testReason")
+    }
+
     private fun getStringFromBuffer(): String {
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
         return stringWriter.toString()
     }
 }
-
-private const val DEVICE_TYPE_TAG = "TEST TYPE"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
new file mode 100644
index 0000000..e8b6f9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
@@ -0,0 +1,703 @@
+/*
+ * 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.systemui.mediaprojection.devicepolicy
+
+import android.app.admin.DevicePolicyManager
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.any
+
+abstract class BaseScreenCaptureDevicePolicyResolverTest(private val precondition: Preconditions) :
+    SysuiTestCase() {
+
+    abstract class Preconditions(
+        val personalScreenCaptureDisabled: Boolean,
+        val workScreenCaptureDisabled: Boolean,
+        val disallowShareIntoManagedProfile: Boolean
+    )
+
+    protected val devicePolicyManager: DevicePolicyManager = mock()
+    protected val userManager: UserManager = mock()
+
+    protected val personalUserHandle: UserHandle = UserHandle.of(123)
+    protected val workUserHandle: UserHandle = UserHandle.of(456)
+
+    protected val policyResolver =
+        ScreenCaptureDevicePolicyResolver(
+            devicePolicyManager,
+            userManager,
+            personalUserHandle,
+            workUserHandle
+        )
+
+    @Before
+    fun setUp() {
+        setUpPolicies()
+    }
+
+    private fun setUpPolicies() {
+        whenever(
+                devicePolicyManager.getScreenCaptureDisabled(
+                    any(),
+                    eq(personalUserHandle.identifier)
+                )
+            )
+            .thenReturn(precondition.personalScreenCaptureDisabled)
+
+        whenever(devicePolicyManager.getScreenCaptureDisabled(any(), eq(workUserHandle.identifier)))
+            .thenReturn(precondition.workScreenCaptureDisabled)
+
+        whenever(
+                userManager.hasUserRestrictionForUser(
+                    eq(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE),
+                    eq(workUserHandle)
+                )
+            )
+            .thenReturn(precondition.disallowShareIntoManagedProfile)
+    }
+}
+
+@RunWith(Parameterized::class)
+@SmallTest
+class IsAllowedScreenCaptureDevicePolicyResolverTest(
+    private val test: IsScreenCaptureAllowedTestCase
+) : BaseScreenCaptureDevicePolicyResolverTest(test.given) {
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() =
+            listOf(
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false,
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true,
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false,
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true,
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = true,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+                IsScreenCaptureAllowedTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            isTargetInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureAllowed = false,
+                ),
+            )
+    }
+
+    class Preconditions(
+        personalScreenCaptureDisabled: Boolean,
+        workScreenCaptureDisabled: Boolean,
+        disallowShareIntoManagedProfile: Boolean,
+        val isHostInWorkProfile: Boolean,
+        val isTargetInWorkProfile: Boolean,
+    ) :
+        BaseScreenCaptureDevicePolicyResolverTest.Preconditions(
+            personalScreenCaptureDisabled,
+            workScreenCaptureDisabled,
+            disallowShareIntoManagedProfile
+        )
+
+    data class IsScreenCaptureAllowedTestCase(
+        val given: Preconditions,
+        val expectedScreenCaptureAllowed: Boolean
+    ) {
+        override fun toString(): String =
+            "isScreenCaptureAllowed: " +
+                "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " +
+                "target[${if (given.isTargetInWorkProfile) "work" else "personal"} profile], " +
+                "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " +
+                "work screen capture disabled = ${given.workScreenCaptureDisabled}, " +
+                "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " +
+                "expected screen capture allowed = $expectedScreenCaptureAllowed"
+    }
+
+    @Test
+    fun test() {
+        val targetAppUserHandle =
+            if (test.given.isTargetInWorkProfile) workUserHandle else personalUserHandle
+        val hostAppUserHandle =
+            if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle
+
+        val screenCaptureAllowed =
+            policyResolver.isScreenCaptureAllowed(targetAppUserHandle, hostAppUserHandle)
+
+        assertWithMessage("Screen capture policy resolved incorrectly")
+            .that(screenCaptureAllowed)
+            .isEqualTo(test.expectedScreenCaptureAllowed)
+    }
+}
+
+@RunWith(Parameterized::class)
+@SmallTest
+class IsCompletelyNotAllowedScreenCaptureDevicePolicyResolverTest(
+    private val test: IsScreenCaptureCompletelyDisabledTestCase
+) : BaseScreenCaptureDevicePolicyResolverTest(test.given) {
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() =
+            listOf(
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = false,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = false,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = false,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = false,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = false
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                ),
+                IsScreenCaptureCompletelyDisabledTestCase(
+                    given =
+                        Preconditions(
+                            isHostInWorkProfile = true,
+                            personalScreenCaptureDisabled = true,
+                            workScreenCaptureDisabled = true,
+                            disallowShareIntoManagedProfile = true
+                        ),
+                    expectedScreenCaptureCompletelyDisabled = true,
+                )
+            )
+    }
+
+    class Preconditions(
+        personalScreenCaptureDisabled: Boolean,
+        workScreenCaptureDisabled: Boolean,
+        disallowShareIntoManagedProfile: Boolean,
+        val isHostInWorkProfile: Boolean,
+    ) :
+        BaseScreenCaptureDevicePolicyResolverTest.Preconditions(
+            personalScreenCaptureDisabled,
+            workScreenCaptureDisabled,
+            disallowShareIntoManagedProfile
+        )
+
+    data class IsScreenCaptureCompletelyDisabledTestCase(
+        val given: Preconditions,
+        val expectedScreenCaptureCompletelyDisabled: Boolean
+    ) {
+        override fun toString(): String =
+            "isScreenCaptureCompletelyDisabled: " +
+                "host[${if (given.isHostInWorkProfile) "work" else "personal"} profile], " +
+                "personal screen capture disabled = ${given.personalScreenCaptureDisabled}, " +
+                "work screen capture disabled = ${given.workScreenCaptureDisabled}, " +
+                "disallow share into managed profile = ${given.disallowShareIntoManagedProfile}, " +
+                "expected screen capture completely disabled = $expectedScreenCaptureCompletelyDisabled"
+    }
+
+    @Test
+    fun test() {
+        val hostAppUserHandle =
+            if (test.given.isHostInWorkProfile) workUserHandle else personalUserHandle
+
+        val completelyDisabled = policyResolver.isScreenCaptureCompletelyDisabled(hostAppUserHandle)
+
+        assertWithMessage("Screen capture policy resolved incorrectly")
+            .that(completelyDisabled)
+            .isEqualTo(test.expectedScreenCaptureCompletelyDisabled)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
index 9bcfd5b..1a93adc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
@@ -26,6 +26,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.FakeDisplayTracker;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,7 +47,8 @@
 
     @Before
     public void setup() {
-        mFlagsContainer = new SysUiState();
+        FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
+        mFlagsContainer = new SysUiState(displayTracker);
         mCallback = mock(SysUiState.SysUiStateCallback.class);
         mFlagsContainer.addCallback(mCallback);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
index 1bc4719..1a35502 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -31,10 +31,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -69,7 +66,7 @@
         // Expressive applies hue rotations to the theme color. The input theme color has hue
         // 117, ensuring the hue changed significantly is a strong signal styles are being applied.
         ColorScheme colorScheme = new ColorScheme(wallpaperColors, false, Style.EXPRESSIVE);
-        Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().get(6)).getHue(), 0.1);
+        Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().getS500()).getHue(), 0.1);
     }
 
 
@@ -111,7 +108,8 @@
     public void testTertiaryHueWrapsProperly() {
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */);
-        int tertiaryMid = colorScheme.getAccent3().get(colorScheme.getAccent3().size() / 2);
+        int tertiaryMid = colorScheme.getAccent3().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Cam cam = Cam.fromInt(tertiaryMid);
         Assert.assertEquals(cam.getHue(), 50.0, 10.0);
     }
@@ -121,7 +119,8 @@
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                 Style.SPRITZ /* style */);
-        int primaryMid = colorScheme.getAccent1().get(colorScheme.getAccent1().size() / 2);
+        int primaryMid = colorScheme.getAccent1().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Cam cam = Cam.fromInt(primaryMid);
         Assert.assertEquals(cam.getChroma(), 12.0, 1.0);
     }
@@ -131,7 +130,8 @@
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                 Style.VIBRANT /* style */);
-        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+        int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Cam cam = Cam.fromInt(neutralMid);
         Assert.assertTrue("chroma was " + cam.getChroma(), Math.floor(cam.getChroma()) <= 12.0);
     }
@@ -141,7 +141,8 @@
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                 Style.EXPRESSIVE /* style */);
-        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+        int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Cam cam = Cam.fromInt(neutralMid);
         Assert.assertTrue(cam.getChroma() <= 8.0);
     }
@@ -151,10 +152,11 @@
         int colorInt = 0xffB3588A; // H350 C50 T50
         ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
                 Style.MONOCHROMATIC /* style */);
-        int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+        int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+                colorScheme.getShadeCount() / 2);
         Assert.assertTrue(
                 Color.red(neutralMid) == Color.green(neutralMid)
-                && Color.green(neutralMid) == Color.blue(neutralMid)
+                        && Color.green(neutralMid) == Color.blue(neutralMid)
         );
     }
 
@@ -190,15 +192,14 @@
                 xml.append("        <").append(styleName).append(">");
 
                 List<String> colors = new ArrayList<>();
-                for (Stream<Integer> stream: Arrays.asList(colorScheme.getAccent1().stream(),
-                        colorScheme.getAccent2().stream(),
-                        colorScheme.getAccent3().stream(),
-                        colorScheme.getNeutral1().stream(),
-                        colorScheme.getNeutral2().stream())) {
+
+                colorScheme.getAllHues().forEach(schemeHue -> {
                     colors.add("ffffff");
-                    colors.addAll(stream.map(Integer::toHexString).map(s -> s.substring(2)).collect(
-                            Collectors.toList()));
-                }
+                    schemeHue.getAllShades().forEach(tone -> {
+                        colors.add(Integer.toHexString(tone).substring(2));
+                    });
+                });
+
                 xml.append(String.join(",", colors));
                 xml.append("</").append(styleName).append(">\n");
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
index 92652a7..3eb7329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
@@ -40,6 +40,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.After;
@@ -65,6 +66,7 @@
     private ImageReader mReader;
     private NavigationBarView mNavBar;
     private VirtualDisplay mVirtualDisplay;
+    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
     @Before
     public void setup() {
@@ -83,6 +85,7 @@
         mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class,
                 mEdgeBackGestureHandlerFactory);
         mNavBar = new NavigationBarView(context, null);
+        mNavBar.setDisplayTracker(mDisplayTracker);
     }
 
     private Display createVirtualDisplay() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 8b0342e..aacbf8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.SparseArray;
@@ -50,13 +51,14 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.settings.SecureSettings;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.pip.Pip;
 
@@ -81,6 +83,7 @@
     private NavigationBar mDefaultNavBar;
     private NavigationBar mSecondaryNavBar;
     private StaticMockitoSession mMockitoSession;
+    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
     @Mock
     private CommandQueue mCommandQueue;
@@ -92,6 +95,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
         mNavigationBarController = spy(
                 new NavigationBarController(mContext,
                         mock(OverviewProxyService.class),
@@ -109,7 +113,9 @@
                         TaskStackChangeListeners.getTestInstance(),
                         Optional.of(mock(Pip.class)),
                         Optional.of(mock(BackAnimation.class)),
-                        mock(FeatureFlags.class)));
+                        mock(FeatureFlags.class),
+                        mock(SecureSettings.class),
+                        mDisplayTracker));
         initializeNavigationBars();
         mMockitoSession = mockitoSession().mockStatic(Utilities.class).startMocking();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 2ad865e..764ddc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -87,6 +87,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowView;
@@ -495,7 +496,8 @@
                 Optional.of(mock(BackAnimation.class)),
                 mUserContextProvider,
                 mWakefulnessLifecycle,
-                mTaskStackChangeListeners));
+                mTaskStackChangeListeners,
+                new FakeDisplayTracker(mContext)));
     }
 
     private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
index cafd2cf..5270737 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
@@ -36,6 +36,7 @@
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -63,6 +64,7 @@
     IWindowManager mIWindowManager;
 
     private NavigationBarTransitions mTransitions;
+    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
     @Before
     public void setup() {
@@ -86,7 +88,7 @@
         when(navBar.getCurrentView()).thenReturn(navBar);
         when(navBar.findViewById(anyInt())).thenReturn(navBar);
         mTransitions = new NavigationBarTransitions(
-                navBar, mIWindowManager, mLightBarTransitionsFactory);
+                navBar, mIWindowManager, mLightBarTransitionsFactory, mDisplayTracker);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
new file mode 100644
index 0000000..f42bfb8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.systemui.navigationbar.gestural
+
+import android.os.Handler
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import android.view.ViewConfiguration
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.NavigationEdgeBackPlugin
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class BackPanelControllerTest : SysuiTestCase() {
+    companion object {
+        private const val START_X: Float = 0f
+    }
+    private lateinit var mBackPanelController: BackPanelController
+    private lateinit var testableLooper: TestableLooper
+    private var triggerThreshold: Float = 0.0f
+    private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
+    @Mock private lateinit var vibratorHelper: VibratorHelper
+    @Mock private lateinit var windowManager: WindowManager
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var latencyTracker: LatencyTracker
+    @Mock private lateinit var layoutParams: WindowManager.LayoutParams
+    @Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mBackPanelController =
+            BackPanelController(
+                context,
+                windowManager,
+                ViewConfiguration.get(context),
+                Handler.createAsync(Looper.myLooper()),
+                vibratorHelper,
+                configurationController,
+                latencyTracker
+            )
+        mBackPanelController.setLayoutParams(layoutParams)
+        mBackPanelController.setBackCallback(backCallback)
+        mBackPanelController.setIsLeftPanel(true)
+        testableLooper = TestableLooper.get(this)
+        triggerThreshold = mBackPanelController.params.staticTriggerThreshold
+    }
+
+    @Test
+    fun handlesActionDown() {
+        startTouch()
+
+        assertThat(mBackPanelController.currentState)
+            .isEqualTo(BackPanelController.GestureState.GONE)
+    }
+
+    @Test
+    fun staysHiddenBeforeSlopCrossed() {
+        startTouch()
+        // Move just enough to not cross the touch slop
+        continueTouch(START_X + touchSlop - 1)
+
+        assertThat(mBackPanelController.currentState)
+            .isEqualTo(BackPanelController.GestureState.GONE)
+    }
+
+    @Test
+    fun handlesDragSlopCrossed() {
+        startTouch()
+        continueTouch(START_X + touchSlop + 1)
+
+        assertThat(mBackPanelController.currentState)
+            .isEqualTo(BackPanelController.GestureState.ENTRY)
+    }
+
+    @Test
+    fun handlesBackCommitted() {
+        startTouch()
+        // Move once to cross the touch slop
+        continueTouch(START_X + touchSlop.toFloat() + 1)
+        // Move again to cross the back trigger threshold
+        continueTouch(START_X + touchSlop + triggerThreshold + 1)
+
+        assertThat(mBackPanelController.currentState)
+            .isEqualTo(BackPanelController.GestureState.ACTIVE)
+        verify(backCallback).setTriggerBack(true)
+        testableLooper.moveTimeForward(100)
+        testableLooper.processAllMessages()
+        verify(vibratorHelper).vibrate(VIBRATE_ACTIVATED_EFFECT)
+
+        finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1)
+        assertThat(mBackPanelController.currentState)
+            .isEqualTo(BackPanelController.GestureState.FLUNG)
+        verify(backCallback).triggerBack()
+    }
+
+    @Test
+    fun handlesBackCancelled() {
+        startTouch()
+        continueTouch(START_X + touchSlop.toFloat() + 1)
+        continueTouch(
+            START_X + touchSlop + triggerThreshold -
+                mBackPanelController.params.deactivationSwipeTriggerThreshold
+        )
+        clearInvocations(backCallback)
+        Thread.sleep(MIN_DURATION_ACTIVE_ANIMATION)
+        // Move in the opposite direction to cross the deactivation threshold and cancel back
+        continueTouch(START_X)
+
+        assertThat(mBackPanelController.currentState)
+            .isEqualTo(BackPanelController.GestureState.INACTIVE)
+        verify(backCallback).setTriggerBack(false)
+        verify(vibratorHelper).vibrate(VIBRATE_DEACTIVATED_EFFECT)
+
+        finishTouchActionUp(START_X)
+        verify(backCallback).cancelBack()
+    }
+
+    private fun startTouch() {
+        mBackPanelController.onMotionEvent(createMotionEvent(ACTION_DOWN, START_X, 0f))
+    }
+
+    private fun continueTouch(x: Float) {
+        mBackPanelController.onMotionEvent(createMotionEvent(ACTION_MOVE, x, 0f))
+    }
+
+    private fun finishTouchActionUp(x: Float) {
+        mBackPanelController.onMotionEvent(createMotionEvent(ACTION_UP, x, 0f))
+    }
+
+    private fun createMotionEvent(action: Int, x: Float, y: Float): MotionEvent {
+        return MotionEvent.obtain(0L, 0L, action, x, y, 0)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
index 36e02cb..bc67df6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
@@ -16,40 +16,50 @@
 internal class FloatingRotationButtonPositionCalculatorTest(private val testCase: TestCase)
     : SysuiTestCase() {
 
-    private val calculator = FloatingRotationButtonPositionCalculator(
-        MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM
-    )
-
     @Test
     fun calculatePosition() {
-        val position = calculator.calculatePosition(
+        val position = testCase.calculator.calculatePosition(
             testCase.rotation,
             testCase.taskbarVisible,
             testCase.taskbarStashed
         )
-
         assertThat(position).isEqualTo(testCase.expectedPosition)
     }
 
     internal class TestCase(
+        val calculator: FloatingRotationButtonPositionCalculator,
         val rotation: Int,
         val taskbarVisible: Boolean,
         val taskbarStashed: Boolean,
         val expectedPosition: Position
     ) {
         override fun toString(): String =
-            "when rotation = $rotation, " +
+            "when calculator = $calculator, " +
+                "rotation = $rotation, " +
                 "taskbarVisible = $taskbarVisible, " +
                 "taskbarStashed = $taskbarStashed - " +
                 "expected $expectedPosition"
     }
 
     companion object {
+        private const val MARGIN_DEFAULT = 10
+        private const val MARGIN_TASKBAR_LEFT = 20
+        private const val MARGIN_TASKBAR_BOTTOM = 30
+
+        private val posLeftCalculator = FloatingRotationButtonPositionCalculator(
+            MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM, true
+        )
+        private val posRightCalculator = FloatingRotationButtonPositionCalculator(
+            MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM, false
+        )
+
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<TestCase> =
             listOf(
+                // Position left
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_0,
                     taskbarVisible = false,
                     taskbarStashed = false,
@@ -60,6 +70,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_90,
                     taskbarVisible = false,
                     taskbarStashed = false,
@@ -70,6 +81,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_180,
                     taskbarVisible = false,
                     taskbarStashed = false,
@@ -80,6 +92,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_270,
                     taskbarVisible = false,
                     taskbarStashed = false,
@@ -90,6 +103,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_0,
                     taskbarVisible = true,
                     taskbarStashed = false,
@@ -100,6 +114,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_0,
                     taskbarVisible = true,
                     taskbarStashed = true,
@@ -110,6 +125,7 @@
                     )
                 ),
                 TestCase(
+                    calculator = posLeftCalculator,
                     rotation = Surface.ROTATION_90,
                     taskbarVisible = true,
                     taskbarStashed = false,
@@ -118,11 +134,86 @@
                         translationX = -MARGIN_TASKBAR_LEFT,
                         translationY = -MARGIN_TASKBAR_BOTTOM
                     )
+                ),
+
+                // Position right
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_0,
+                    taskbarVisible = false,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.BOTTOM or Gravity.RIGHT,
+                        translationX = -MARGIN_DEFAULT,
+                        translationY = -MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_90,
+                    taskbarVisible = false,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.TOP or Gravity.RIGHT,
+                        translationX = -MARGIN_DEFAULT,
+                        translationY = MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_180,
+                    taskbarVisible = false,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.TOP or Gravity.LEFT,
+                        translationX = MARGIN_DEFAULT,
+                        translationY = MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_270,
+                    taskbarVisible = false,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.BOTTOM or Gravity.LEFT,
+                        translationX = MARGIN_DEFAULT,
+                        translationY = -MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_0,
+                    taskbarVisible = true,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.BOTTOM or Gravity.RIGHT,
+                        translationX = -MARGIN_TASKBAR_LEFT,
+                        translationY = -MARGIN_TASKBAR_BOTTOM
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_0,
+                    taskbarVisible = true,
+                    taskbarStashed = true,
+                    expectedPosition = Position(
+                        gravity = Gravity.BOTTOM or Gravity.RIGHT,
+                        translationX = -MARGIN_DEFAULT,
+                        translationY = -MARGIN_DEFAULT
+                    )
+                ),
+                TestCase(
+                    calculator = posRightCalculator,
+                    rotation = Surface.ROTATION_90,
+                    taskbarVisible = true,
+                    taskbarStashed = false,
+                    expectedPosition = Position(
+                        gravity = Gravity.TOP or Gravity.RIGHT,
+                        translationX = -MARGIN_TASKBAR_LEFT,
+                        translationY = MARGIN_TASKBAR_BOTTOM
+                    )
                 )
             )
-
-        private const val MARGIN_DEFAULT = 10
-        private const val MARGIN_TASKBAR_LEFT = 20
-        private const val MARGIN_TASKBAR_BOTTOM = 30
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
index bbe60f4..18be92b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
@@ -16,17 +16,14 @@
 
 package com.android.systemui.notetask
 
-import android.content.ComponentName
+import android.app.role.RoleManager
 import android.content.Intent
-import android.content.pm.ActivityInfo
-import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
-import android.content.pm.PackageManager.ResolveInfoFlags
-import android.content.pm.ResolveInfo
 import android.test.suitebuilder.annotation.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -47,172 +44,39 @@
 internal class NoteTaskIntentResolverTest : SysuiTestCase() {
 
     @Mock lateinit var packageManager: PackageManager
+    @Mock lateinit var roleManager: RoleManager
 
-    private lateinit var resolver: NoteTaskIntentResolver
+    private lateinit var underTest: NoteTaskIntentResolver
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        resolver = NoteTaskIntentResolver(packageManager)
-    }
-
-    private fun createResolveInfo(
-        activityInfo: ActivityInfo? = createActivityInfo(),
-    ): ResolveInfo {
-        return ResolveInfo().apply { this.activityInfo = activityInfo }
-    }
-
-    private fun createActivityInfo(
-        packageName: String = "PackageName",
-        name: String? = "ActivityName",
-        exported: Boolean = true,
-        enabled: Boolean = true,
-        showWhenLocked: Boolean = true,
-        turnScreenOn: Boolean = true,
-    ): ActivityInfo {
-        return ActivityInfo().apply {
-            this.name = name
-            this.exported = exported
-            this.enabled = enabled
-            if (showWhenLocked) {
-                flags = flags or ActivityInfo.FLAG_SHOW_WHEN_LOCKED
-            }
-            if (turnScreenOn) {
-                flags = flags or ActivityInfo.FLAG_TURN_SCREEN_ON
-            }
-            this.applicationInfo = ApplicationInfo().apply { this.packageName = packageName }
-        }
-    }
-
-    private fun givenQueryIntentActivities(block: () -> List<ResolveInfo>) {
-        whenever(packageManager.queryIntentActivities(any(), any<ResolveInfoFlags>()))
-            .thenReturn(block())
-    }
-
-    private fun givenResolveActivity(block: () -> ResolveInfo?) {
-        whenever(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenReturn(block())
+        underTest = NoteTaskIntentResolver(context, roleManager)
     }
 
     @Test
-    fun resolveIntent_shouldReturnNotesIntent() {
-        givenQueryIntentActivities { listOf(createResolveInfo()) }
-        givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo()) }
+    fun resolveIntent_shouldReturnIntentInStylusMode() {
+        val packageName = "com.android.note.app"
+        whenever(roleManager.getRoleHoldersAsUser(NoteTaskIntentResolver.ROLE_NOTES, context.user))
+            .then { listOf(packageName) }
 
-        val actual = resolver.resolveIntent()
+        val actual = underTest.resolveIntent()
 
-        val expected =
-            Intent(ACTION_CREATE_NOTE)
-                .setPackage("PackageName")
-                .setComponent(ComponentName("PackageName", "ActivityName"))
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        // Compares the string representation of both intents, as they are different instances.
-        assertThat(actual.toString()).isEqualTo(expected.toString())
+        requireNotNull(actual) { "Intent must not be null" }
+        assertThat(actual.action).isEqualTo(ACTION_CREATE_NOTE)
+        assertThat(actual.`package`).isEqualTo(packageName)
+        val expectedExtra = actual.getExtra(NoteTaskIntentResolver.INTENT_EXTRA_USE_STYLUS_MODE)
+        assertThat(expectedExtra).isEqualTo(true)
+        val expectedFlag = actual.flags and Intent.FLAG_ACTIVITY_NEW_TASK
+        assertThat(expectedFlag).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
     }
 
     @Test
-    fun resolveIntent_activityInfoEnabledIsFalse_shouldReturnNull() {
-        givenQueryIntentActivities { listOf(createResolveInfo()) }
-        givenResolveActivity {
-            createResolveInfo(activityInfo = createActivityInfo(enabled = false))
-        }
+    fun resolveIntent_noRoleHolderIsSet_shouldReturnNull() {
+        whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskIntentResolver.ROLE_NOTES), any()))
+            .then { listOf<String>() }
 
-        val actual = resolver.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-
-    @Test
-    fun resolveIntent_activityInfoExportedIsFalse_shouldReturnNull() {
-        givenQueryIntentActivities { listOf(createResolveInfo()) }
-        givenResolveActivity {
-            createResolveInfo(activityInfo = createActivityInfo(exported = false))
-        }
-
-        val actual = resolver.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-
-    @Test
-    fun resolveIntent_activityInfoShowWhenLockedIsFalse_shouldReturnNull() {
-        givenQueryIntentActivities { listOf(createResolveInfo()) }
-        givenResolveActivity {
-            createResolveInfo(activityInfo = createActivityInfo(showWhenLocked = false))
-        }
-
-        val actual = resolver.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-
-    @Test
-    fun resolveIntent_activityInfoTurnScreenOnIsFalse_shouldReturnNull() {
-        givenQueryIntentActivities { listOf(createResolveInfo()) }
-        givenResolveActivity {
-            createResolveInfo(activityInfo = createActivityInfo(turnScreenOn = false))
-        }
-
-        val actual = resolver.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-
-    @Test
-    fun resolveIntent_activityInfoNameIsBlank_shouldReturnNull() {
-        givenQueryIntentActivities { listOf(createResolveInfo()) }
-        givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = "")) }
-
-        val actual = resolver.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-
-    @Test
-    fun resolveIntent_activityInfoNameIsNull_shouldReturnNull() {
-        givenQueryIntentActivities { listOf(createResolveInfo()) }
-        givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = null)) }
-
-        val actual = resolver.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-
-    @Test
-    fun resolveIntent_activityInfoIsNull_shouldReturnNull() {
-        givenQueryIntentActivities { listOf(createResolveInfo()) }
-        givenResolveActivity { createResolveInfo(activityInfo = null) }
-
-        val actual = resolver.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-
-    @Test
-    fun resolveIntent_resolveActivityIsNull_shouldReturnNull() {
-        givenQueryIntentActivities { listOf(createResolveInfo()) }
-        givenResolveActivity { null }
-
-        val actual = resolver.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-
-    @Test
-    fun resolveIntent_packageNameIsBlank_shouldReturnNull() {
-        givenQueryIntentActivities {
-            listOf(createResolveInfo(createActivityInfo(packageName = "")))
-        }
-
-        val actual = resolver.resolveIntent()
-
-        assertThat(actual).isNull()
-    }
-
-    @Test
-    fun resolveIntent_activityNotFoundForAction_shouldReturnNull() {
-        givenQueryIntentActivities { emptyList() }
-
-        val actual = resolver.resolveIntent()
+        val actual = underTest.resolveIntent()
 
         assertThat(actual).isNull()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
new file mode 100644
index 0000000..a1d42a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.notetask.quickaffordance
+
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskQuickAffordanceConfig].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskQuickAffordanceConfigTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
+
+    @Mock lateinit var noteTaskController: NoteTaskController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(noteTaskController.showNoteTask()).then {}
+    }
+
+    private fun createUnderTest(isEnabled: Boolean) =
+        NoteTaskQuickAffordanceConfig(
+            context = context,
+            noteTaskController = noteTaskController,
+            isEnabled = isEnabled,
+        )
+
+    @Test
+    fun lockScreenState_isNotEnabled_shouldEmitHidden() = runTest {
+        val underTest = createUnderTest(isEnabled = false)
+
+        val actual = collectLastValue(underTest.lockScreenState)
+
+        assertThat(actual()).isEqualTo(LockScreenState.Hidden)
+    }
+
+    @Test
+    fun lockScreenState_isEnabled_shouldEmitVisible() = runTest {
+        val stringResult = "Notetaking"
+        val underTest = createUnderTest(isEnabled = true)
+
+        val actual = collectLastValue(underTest.lockScreenState)
+
+        val expected =
+            LockScreenState.Visible(
+                icon =
+                    Icon.Resource(
+                        res = R.drawable.ic_note_task_shortcut_keyguard,
+                        contentDescription = ContentDescription.Loaded(stringResult),
+                    )
+            )
+        assertThat(actual()).isEqualTo(expected)
+    }
+
+    @Test
+    fun onTriggered_shouldLaunchNoteTask() {
+        val underTest = createUnderTest(isEnabled = false)
+
+        underTest.onTriggered(expandable = null)
+
+        verify(noteTaskController).showNoteTask()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index a56990f..4a6158f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
@@ -53,6 +54,7 @@
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.NotificationChannels;
 
@@ -82,6 +84,8 @@
     @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private View mView;
 
     private BroadcastReceiver mReceiver;
@@ -103,8 +107,12 @@
         mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager);
         ActivityStarter starter = mDependency.injectMockDependency(ActivityStarter.class);
         BroadcastSender broadcastSender = mDependency.injectMockDependency(BroadcastSender.class);
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+        when(mUserTracker.getUserHandle()).thenReturn(
+                UserHandle.of(ActivityManager.getCurrentUser()));
         mPowerNotificationWarnings = new PowerNotificationWarnings(wrapper, starter,
-                broadcastSender, () -> mBatteryController, mDialogLaunchAnimator, mUiEventLogger);
+                broadcastSender, () -> mBatteryController, mDialogLaunchAnimator, mUiEventLogger,
+                mUserTracker);
         BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1,
                 BatteryManager.BATTERY_HEALTH_GOOD, 5, 15);
         mPowerNotificationWarnings.updateSnapshot(snapshot);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
index 43fb1bd..dee1cc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
@@ -59,6 +59,7 @@
 @SmallTest
 public class AutoAddTrackerTest extends SysuiTestCase {
 
+    private static final int END_POSITION = -1;
     private static final int USER = 0;
 
     @Mock
@@ -142,6 +143,29 @@
     }
 
     @Test
+    public void testRestoredTilePositionPreserved() {
+        verify(mBroadcastDispatcher).registerReceiver(
+                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
+        String restoredTiles = "saver,internet,work,cast";
+        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+        assertEquals(2, mAutoTracker.getRestoredTilePosition("work"));
+    }
+
+    @Test
+    public void testNoRestoredTileReturnsEndPosition() {
+        verify(mBroadcastDispatcher).registerReceiver(
+                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
+        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, null);
+
+        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+        assertEquals(END_POSITION, mAutoTracker.getRestoredTilePosition("work"));
+    }
+
+    @Test
     public void testBroadcastReceiverRegistered() {
         verify(mBroadcastDispatcher).registerReceiver(
                 any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 42ef9c2..4caa50f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -60,6 +60,7 @@
 import com.android.systemui.qs.external.TileServiceRequestController;
 import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -494,7 +495,7 @@
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         MockitoAnnotations.initMocks(this);
-        CommandQueue commandQueue = new CommandQueue(context);
+        CommandQueue commandQueue = new CommandQueue(context, new FakeDisplayTracker(context));
 
         setupQsComponent();
         setUpViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 2bd068a..8644b5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -35,12 +35,14 @@
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.LaunchableFrameLayout
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
@@ -90,6 +92,7 @@
     private lateinit var customTile: CustomTile
     private lateinit var testableLooper: TestableLooper
     private lateinit var customTileBuilder: CustomTile.Builder
+    private val displayTracker = FakeDisplayTracker(mContext)
 
     @Before
     fun setUp() {
@@ -119,7 +122,8 @@
                 activityStarter,
                 qsLogger,
                 customTileStatePersister,
-                tileServices
+                tileServices,
+                displayTracker
         )
 
         customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
@@ -339,7 +343,7 @@
         val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
         tile.qsTile.activityLaunchForClick = pi
 
-        tile.handleClick(mock(View::class.java))
+        tile.handleClick(mock(LaunchableFrameLayout::class.java))
 
         testableLooper.processAllMessages()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index 80c39cf..addca9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -37,10 +37,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.connectivity.WifiIndicators;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -135,4 +137,24 @@
         assertThat(mTile.getState().secondaryLabel)
             .isNotEqualTo(mContext.getString(R.string.status_bar_airplane));
     }
+
+    @Test
+    public void setIsAirplaneMode_APM_enabled_after_wifi_disconnected() {
+        WifiIndicators wifiIndicators = new WifiIndicators(
+            /* enabled= */ true,
+            /* statusIcon= */ null,
+            /* qsIcon= */ null,
+            /* activityIn= */ false,
+            /* activityOut= */ false,
+            /* description= */ null,
+            /* isTransient= */ false,
+            /* statusLabel= */ null
+        );
+        mTile.mSignalCallback.setWifiIndicators(wifiIndicators);
+        IconState state = new IconState(true, 0, "");
+        mTile.mSignalCallback.setIsAirplaneMode(state);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.getState().icon).isEqualTo(
+                QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 69f3e987..33aaa3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -16,13 +16,15 @@
 
 package com.android.systemui.screenrecord;
 
+import static com.google.common.truth.Truth.assertThat;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.Dialog;
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.os.Looper;
@@ -31,7 +33,13 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -61,8 +69,15 @@
     @Mock
     private UserContextProvider mUserContextProvider;
     @Mock
+    private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver;
+    @Mock
+    private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    @Mock
     private UserTracker mUserTracker;
 
+    private FakeFeatureFlags mFeatureFlags;
     private RecordingController mController;
 
     private static final int USER_ID = 10;
@@ -70,8 +85,9 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new RecordingController(mMainExecutor, mBroadcastDispatcher,
-                mUserContextProvider, mUserTracker);
+        mFeatureFlags = new FakeFeatureFlags();
+        mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext,
+                mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker);
         mController.addCallback(mCallback);
     }
 
@@ -190,4 +206,67 @@
         verify(mCallback).onRecordingEnd();
         assertFalse(mController.isRecording());
     }
+
+    @Test
+    public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
+        when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
+
+        Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+                mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+        assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
+    }
+
+    @Test
+    public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
+
+        Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+                mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+        assertThat(dialog).isInstanceOf(ScreenRecordDialog.class);
+    }
+
+    @Test
+    public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+        when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
+
+        Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+                mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+        assertThat(dialog).isInstanceOf(ScreenCaptureDisabledDialog.class);
+    }
+
+    @Test
+    public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+        when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
+
+        Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+                mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+        assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 0aa3621..5b094c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.screenrecord
 
+import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -59,6 +60,7 @@
         dialog =
             ScreenRecordPermissionDialog(
                 context,
+                UserHandle.of(0),
                 controller,
                 starter,
                 dialogLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
index b6a595b..7ba2cf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -35,9 +35,33 @@
     @Test
     fun testCreateShareIntent() {
         val uri = Uri.parse("content://fake")
+
+        val output = ActionIntentCreator.createShareIntent(uri)
+
+        assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER)
+        assertFlagsSet(
+            Intent.FLAG_ACTIVITY_NEW_TASK or
+                Intent.FLAG_ACTIVITY_CLEAR_TASK or
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+            output.flags
+        )
+
+        val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+        assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND)
+        assertThat(wrappedIntent?.data).isEqualTo(uri)
+        assertThat(wrappedIntent?.type).isEqualTo("image/png")
+        assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull()
+        assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isNull()
+        assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
+            .isEqualTo(uri)
+    }
+
+    @Test
+    fun testCreateShareIntentWithSubject() {
+        val uri = Uri.parse("content://fake")
         val subject = "Example subject"
 
-        val output = ActionIntentCreator.createShareIntent(uri, subject)
+        val output = ActionIntentCreator.createShareIntentWithSubject(uri, subject)
 
         assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER)
         assertFlagsSet(
@@ -52,16 +76,34 @@
         assertThat(wrappedIntent?.data).isEqualTo(uri)
         assertThat(wrappedIntent?.type).isEqualTo("image/png")
         assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isEqualTo(subject)
+        assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isNull()
         assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
             .isEqualTo(uri)
     }
 
     @Test
-    fun testCreateShareIntent_noSubject() {
+    fun testCreateShareIntentWithExtraText() {
         val uri = Uri.parse("content://fake")
-        val output = ActionIntentCreator.createShareIntent(uri, null)
+        val extraText = "Extra text"
+
+        val output = ActionIntentCreator.createShareIntentWithExtraText(uri, extraText)
+
+        assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER)
+        assertFlagsSet(
+            Intent.FLAG_ACTIVITY_NEW_TASK or
+                Intent.FLAG_ACTIVITY_CLEAR_TASK or
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+            output.flags
+        )
+
         val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+        assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND)
+        assertThat(wrappedIntent?.data).isEqualTo(uri)
+        assertThat(wrappedIntent?.type).isEqualTo("image/png")
         assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull()
+        assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_TEXT)).isEqualTo(extraText)
+        assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
+            .isEqualTo(uri)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
index e1eda11..d5014fa36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
@@ -39,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -67,6 +68,7 @@
     private PendingIntent mMockPendingIntent;
 
     private Intent mIntent;
+    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
     @Before
     public void setup() throws InterruptedException, ExecutionException, TimeoutException {
@@ -135,10 +137,11 @@
         if (withStatusBar) {
             return new ActionProxyReceiver(
                     Optional.of(mMockCentralSurfaces), mMockActivityManagerWrapper,
-                    mMockScreenshotSmartActions);
+                    mMockScreenshotSmartActions, mDisplayTracker);
         } else {
             return new ActionProxyReceiver(
-                    Optional.empty(), mMockActivityManagerWrapper, mMockScreenshotSmartActions);
+                    Optional.empty(), mMockActivityManagerWrapper, mMockScreenshotSmartActions,
+                    mDisplayTracker);
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
new file mode 100644
index 0000000..9f0a803
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/MessageContainerControllerTest.kt
@@ -0,0 +1,143 @@
+package com.android.systemui.screenshot
+
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.Guideline
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MessageContainerControllerTest : SysuiTestCase() {
+    lateinit var messageContainer: MessageContainerController
+
+    @Mock lateinit var workProfileMessageController: WorkProfileMessageController
+
+    @Mock lateinit var screenshotDetectionController: ScreenshotDetectionController
+
+    @Mock lateinit var icon: Drawable
+
+    lateinit var workProfileFirstRunView: ViewGroup
+    lateinit var detectionNoticeView: ViewGroup
+    lateinit var container: FrameLayout
+
+    var featureFlags = FakeFeatureFlags()
+    lateinit var screenshotView: ViewGroup
+
+    val userHandle = UserHandle.of(5)
+    val screenshotData = ScreenshotData.forTesting()
+
+    val appName = "app name"
+    lateinit var workProfileData: WorkProfileMessageController.WorkProfileFirstRunData
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        messageContainer =
+            MessageContainerController(
+                workProfileMessageController,
+                screenshotDetectionController,
+                featureFlags
+            )
+        screenshotView = ConstraintLayout(mContext)
+        workProfileData = WorkProfileMessageController.WorkProfileFirstRunData(appName, icon)
+
+        val guideline = Guideline(mContext)
+        guideline.id = com.android.systemui.R.id.guideline
+        screenshotView.addView(guideline)
+
+        container = FrameLayout(mContext)
+        container.id = com.android.systemui.R.id.screenshot_message_container
+        screenshotView.addView(container)
+
+        workProfileFirstRunView = FrameLayout(mContext)
+        workProfileFirstRunView.id = com.android.systemui.R.id.work_profile_first_run
+        container.addView(workProfileFirstRunView)
+
+        detectionNoticeView = FrameLayout(mContext)
+        detectionNoticeView.id = com.android.systemui.R.id.screenshot_detection_notice
+        container.addView(detectionNoticeView)
+
+        messageContainer.setView(screenshotView)
+
+        screenshotData.userHandle = userHandle
+    }
+
+    @Test
+    fun testOnScreenshotTakenUserHandle_noWorkProfileFirstRun() {
+        featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+        // (just being explicit here)
+        whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle))).thenReturn(null)
+
+        messageContainer.onScreenshotTaken(userHandle)
+
+        verify(workProfileMessageController, never()).populateView(any(), any(), any())
+    }
+
+    @Test
+    fun testOnScreenshotTakenUserHandle_noWorkProfileFlag() {
+        featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+        messageContainer.onScreenshotTaken(userHandle)
+
+        verify(workProfileMessageController, never()).onScreenshotTaken(any())
+        verify(workProfileMessageController, never()).populateView(any(), any(), any())
+    }
+
+    @Test
+    fun testOnScreenshotTakenUserHandle_withWorkProfileFirstRun() {
+        featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+        whenever(workProfileMessageController.onScreenshotTaken(eq(userHandle)))
+            .thenReturn(workProfileData)
+        messageContainer.onScreenshotTaken(userHandle)
+
+        verify(workProfileMessageController)
+            .populateView(eq(workProfileFirstRunView), eq(workProfileData), any())
+        assertEquals(View.VISIBLE, workProfileFirstRunView.visibility)
+        assertEquals(View.GONE, detectionNoticeView.visibility)
+    }
+
+    @Test
+    fun testOnScreenshotTakenScreenshotData_flagsOff() {
+        featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+        featureFlags.set(Flags.SCREENSHOT_DETECTION, false)
+
+        messageContainer.onScreenshotTaken(screenshotData)
+
+        verify(workProfileMessageController, never()).onScreenshotTaken(any())
+        verify(screenshotDetectionController, never()).maybeNotifyOfScreenshot(any())
+
+        assertEquals(View.GONE, container.visibility)
+    }
+
+    @Test
+    fun testOnScreenshotTakenScreenshotData_nothingToShow() {
+        featureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+        featureFlags.set(Flags.SCREENSHOT_DETECTION, true)
+
+        messageContainer.onScreenshotTaken(screenshotData)
+
+        verify(workProfileMessageController, never()).populateView(any(), any(), any())
+        verify(screenshotDetectionController, never()).populateView(any(), any())
+
+        assertEquals(View.GONE, container.visibility)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 46a502a..2e73c0b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -22,15 +22,12 @@
 import android.graphics.Insets
 import android.graphics.Rect
 import android.hardware.HardwareBuffer
-import android.os.Bundle
 import android.os.UserHandle
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
@@ -38,6 +35,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
+import org.junit.Assert
 import org.junit.Test
 
 private const val USER_ID = 1
@@ -49,7 +47,6 @@
     private val bounds = Rect(25, 25, 75, 75)
 
     private val scope = CoroutineScope(Dispatchers.Unconfined)
-    private val dispatcher = Dispatchers.Unconfined
     private val policy = FakeScreenshotPolicy()
     private val flags = FakeFeatureFlags()
 
@@ -58,7 +55,8 @@
     fun testProcessAsync() {
         flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         var result: ScreenshotRequest? = null
@@ -76,17 +74,49 @@
         assertThat(result).isEqualTo(request)
     }
 
+    /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
+    @Test
+    fun testProcessAsync_ScreenshotData() {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
+
+        val request =
+            ScreenshotData.fromRequest(
+                ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
+            )
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        var result: ScreenshotData? = null
+        var callbackCount = 0
+        val callback: (ScreenshotData) -> Unit = { processedRequest: ScreenshotData ->
+            result = processedRequest
+            callbackCount++
+        }
+
+        // runs synchronously, using Unconfined Dispatcher
+        processor.processAsync(request, callback)
+
+        // Callback invoked once returning the same request (no changes)
+        assertThat(callbackCount).isEqualTo(1)
+        assertThat(result).isEqualTo(request)
+    }
+
     @Test
     fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
         flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         val processedRequest = processor.process(request)
 
         // No changes
         assertThat(processedRequest).isEqualTo(request)
+
+        val screenshotData = ScreenshotData.fromRequest(request)
+        val processedData = processor.process(screenshotData)
+
+        assertThat(processedData).isEqualTo(screenshotData)
     }
 
     @Test
@@ -97,9 +127,11 @@
         policy.setManagedProfile(USER_ID, false)
         policy.setDisplayContentInfo(
             policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
+            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+        )
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         val processedRequest = processor.process(request)
@@ -108,6 +140,13 @@
         assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
         assertThat(processedRequest.source).isEqualTo(SCREENSHOT_OTHER)
         assertThat(processedRequest.topComponent).isEqualTo(component)
+
+        val processedData = processor.process(ScreenshotData.fromRequest(request))
+
+        // Request has topComponent added, but otherwise unchanged.
+        assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
+        assertThat(processedData.source).isEqualTo(SCREENSHOT_OTHER)
+        assertThat(processedData.topComponent).isEqualTo(component)
     }
 
     @Test
@@ -120,23 +159,64 @@
 
         // Indicate that the primary content belongs to a manged profile
         policy.setManagedProfile(USER_ID, true)
-        policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
-            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
+        policy.setDisplayContentInfo(
+            policy.getDefaultDisplayId(),
+            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+        )
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         val processedRequest = processor.process(request)
 
         // Expect a task snapshot is taken, overriding the full screen mode
         assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
-        assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+        assertThat(bitmap.equalsHardwareBitmap(processedRequest.bitmap)).isTrue()
         assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
         assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
         assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
         assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
         assertThat(processedRequest.userId).isEqualTo(USER_ID)
         assertThat(processedRequest.topComponent).isEqualTo(component)
+
+        val processedData = processor.process(ScreenshotData.fromRequest(request))
+
+        // Expect a task snapshot is taken, overriding the full screen mode
+        assertThat(processedData.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
+        assertThat(processedData.bitmap).isEqualTo(bitmap)
+        assertThat(processedData.screenBounds).isEqualTo(bounds)
+        assertThat(processedData.insets).isEqualTo(Insets.NONE)
+        assertThat(processedData.taskId).isEqualTo(TASK_ID)
+        assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
+        assertThat(processedRequest.userId).isEqualTo(USER_ID)
+        assertThat(processedRequest.topComponent).isEqualTo(component)
+    }
+
+    @Test
+    fun testFullScreenshot_managedProfile_nullBitmap() {
+        flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true)
+
+        // Provide a null task bitmap when asked
+        imageCapture.image = null
+
+        // Indicate that the primary content belongs to a manged profile
+        policy.setManagedProfile(USER_ID, true)
+        policy.setDisplayContentInfo(
+            policy.getDefaultDisplayId(),
+            DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+        )
+
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
+        val processor = RequestProcessor(imageCapture, policy, flags, scope)
+
+        Assert.assertThrows(IllegalStateException::class.java) {
+            runBlocking { processor.process(request) }
+        }
+        Assert.assertThrows(IllegalStateException::class.java) {
+            runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
+        }
     }
 
     @Test
@@ -147,15 +227,26 @@
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         val bitmap = makeHardwareBitmap(100, 100)
-        val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
-            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                .setTopComponent(component)
+                .setTaskId(TASK_ID)
+                .setUserId(USER_ID)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(bounds)
+                .setInsets(Insets.NONE)
+                .build()
 
         val processedRequest = processor.process(request)
 
         // No changes
         assertThat(processedRequest).isEqualTo(request)
+
+        val screenshotData = ScreenshotData.fromRequest(request)
+        val processedData = processor.process(screenshotData)
+
+        assertThat(processedData).isEqualTo(screenshotData)
     }
 
     @Test
@@ -168,15 +259,26 @@
         policy.setManagedProfile(USER_ID, false)
 
         val bitmap = makeHardwareBitmap(100, 100)
-        val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
-            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                .setTopComponent(component)
+                .setTaskId(TASK_ID)
+                .setUserId(USER_ID)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(bounds)
+                .setInsets(Insets.NONE)
+                .build()
 
         val processedRequest = processor.process(request)
 
         // No changes
         assertThat(processedRequest).isEqualTo(request)
+
+        val screenshotData = ScreenshotData.fromRequest(request)
+        val processedData = processor.process(screenshotData)
+
+        assertThat(processedData).isEqualTo(screenshotData)
     }
 
     @Test
@@ -190,26 +292,41 @@
         policy.setManagedProfile(USER_ID, true)
 
         val bitmap = makeHardwareBitmap(100, 100)
-        val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
-            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                .setTopComponent(component)
+                .setTaskId(TASK_ID)
+                .setUserId(USER_ID)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(bounds)
+                .setInsets(Insets.NONE)
+                .build()
 
         val processedRequest = processor.process(request)
 
         // Work profile, but already a task snapshot, so no changes
         assertThat(processedRequest).isEqualTo(request)
+
+        val screenshotData = ScreenshotData.fromRequest(request)
+        val processedData = processor.process(screenshotData)
+
+        assertThat(processedData).isEqualTo(screenshotData)
     }
 
     private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
-        val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
-            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+        val buffer =
+            HardwareBuffer.create(
+                width,
+                height,
+                HardwareBuffer.RGBA_8888,
+                1,
+                HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+            )
         return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
     }
 
-    private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean {
-        val provided = bundleToHardwareBitmap(bundle)
-        return provided.hardwareBuffer == this.hardwareBuffer &&
-                provided.colorSpace == this.colorSpace
+    private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
+        return bitmap.hardwareBuffer == this.hardwareBuffer && bitmap.colorSpace == this.colorSpace
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
new file mode 100644
index 0000000..43e9939
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.systemui.screenshot
+
+import android.content.ComponentName
+import android.graphics.Insets
+import android.graphics.Rect
+import android.os.UserHandle
+import android.view.WindowManager
+import com.android.internal.util.ScreenshotRequest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ScreenshotDataTest {
+    private val type = WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+    private val source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
+    private val bounds = Rect(1, 2, 3, 4)
+    private val taskId = 123
+    private val userId = 1
+    private val insets = Insets.of(1, 2, 3, 4)
+    private val component = ComponentName("android.test", "android.test.Component")
+
+    @Test
+    fun testConstruction() {
+        val request =
+            ScreenshotRequest.Builder(type, source)
+                .setBoundsOnScreen(bounds)
+                .setInsets(insets)
+                .setTaskId(taskId)
+                .setUserId(userId)
+                .setTopComponent(component)
+                .build()
+
+        val data = ScreenshotData.fromRequest(request)
+
+        assertThat(data.source).isEqualTo(source)
+        assertThat(data.type).isEqualTo(type)
+        assertThat(data.screenBounds).isEqualTo(bounds)
+        assertThat(data.insets).isEqualTo(insets)
+        assertThat(data.taskId).isEqualTo(taskId)
+        assertThat(data.userHandle).isEqualTo(UserHandle.of(userId))
+        assertThat(data.topComponent).isEqualTo(component)
+    }
+
+    @Test
+    fun testNegativeUserId() {
+        val request = ScreenshotRequest.Builder(type, source).setUserId(-1).build()
+
+        val data = ScreenshotData.fromRequest(request)
+
+        assertThat(data.userHandle).isNull()
+    }
+
+    @Test
+    fun testPackageNameAsString() {
+        val request = ScreenshotRequest.Builder(type, source).setTopComponent(component).build()
+
+        val data = ScreenshotData.fromRequest(request)
+
+        assertThat(data.packageNameString).isEqualTo("android.test")
+    }
+
+    @Test
+    fun testPackageNameAsString_null() {
+        val request = ScreenshotRequest.Builder(type, source).build()
+
+        val data = ScreenshotData.fromRequest(request)
+
+        assertThat(data.packageNameString).isEqualTo("")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
index 17396b1..e70fa2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt
@@ -31,6 +31,7 @@
 import android.testing.AndroidTestingRunner
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
+import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
@@ -126,8 +127,10 @@
         val userManager = mock<UserManager>()
         val atmService = mock<IActivityTaskManager>()
         val dispatcher = Dispatchers.Unconfined
+        val displayTracker = FakeDisplayTracker(mContext)
 
-        return object : ScreenshotPolicyImpl(context, userManager, atmService, dispatcher) {
+        return object : ScreenshotPolicyImpl(context, userManager, atmService, dispatcher,
+                displayTracker) {
             override suspend fun isManagedProfile(userId: Int) = (userId == MANAGED_PROFILE_USER)
             override suspend fun getAllRootTaskInfosOnDisplay(displayId: Int) = tasks
             override suspend fun isNotificationShadeExpanded() = shadeExpanded
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 99c79b0..1fa2ace 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -29,24 +29,26 @@
 import android.os.UserHandle
 import android.os.UserManager
 import android.testing.AndroidTestingRunner
-import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.ScreenshotHelper
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.SCREENSHOT_METADATA
 import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
-import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
 import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW
 import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argThat
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import java.util.function.Consumer
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -55,10 +57,10 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doThrow
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
 
 private const val USER_ID = 1
 private const val TASK_ID = 11
@@ -80,27 +82,51 @@
     private val flags = FakeFeatureFlags()
     private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
 
-    private val service = TakeScreenshotService(
-        controller, userManager, devicePolicyManager, eventLogger,
-        notificationsController, mContext, Runnable::run, flags, requestProcessor)
+    private val service =
+        TakeScreenshotService(
+            controller,
+            userManager,
+            devicePolicyManager,
+            eventLogger,
+            notificationsController,
+            mContext,
+            Runnable::run,
+            flags,
+            requestProcessor
+        )
 
     @Before
     fun setUp() {
         whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
-        whenever(devicePolicyManager.getScreenCaptureDisabled(
-            /* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL)))
+        whenever(
+                devicePolicyManager.getScreenCaptureDisabled(
+                    /* admin component (null: any admin) */ isNull(),
+                    eq(UserHandle.USER_ALL)
+                )
+            )
             .thenReturn(false)
         whenever(userManager.isUserUnlocked).thenReturn(true)
 
         // Stub request processor as a synchronous no-op for tests with the flag enabled
         doAnswer {
-            val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
-            val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
-            consumer.accept(request)
-        }.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any())
+                val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
+                val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
+                consumer.accept(request)
+            }
+            .whenever(requestProcessor)
+            .processAsync(/* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
+
+        doAnswer {
+                val request: ScreenshotData = it.getArgument(0) as ScreenshotData
+                val consumer: Consumer<ScreenshotData> = it.getArgument(1)
+                consumer.accept(request)
+            }
+            .whenever(requestProcessor)
+            .processAsync(/* screenshot= */ any(ScreenshotData::class.java), /* callback= */ any())
 
         // Flipped in selected test cases
         flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
+        flags.set(SCREENSHOT_METADATA, false)
 
         service.attach(
             mContext,
@@ -108,7 +134,8 @@
             /* className = */ null,
             /* token = */ null,
             application,
-            /* activityManager = */ null)
+            /* activityManager = */ null
+        )
     }
 
     @Test
@@ -125,103 +152,399 @@
 
     @Test
     fun takeScreenshotFullscreen() {
-        val request = ScreenshotRequest(
-            TAKE_SCREENSHOT_FULLSCREEN,
-            SCREENSHOT_KEY_CHORD,
-            topComponent)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+                .setTopComponent(topComponent)
+                .build()
 
-        service.handleRequest(request, { /* onSaved */ }, callback)
+        service.handleRequest(request, { /* onSaved */}, callback)
 
-        verify(controller, times(1)).takeScreenshotFullscreen(
-            eq(topComponent),
-            /* onSavedListener = */ any(),
-            /* requestCallback = */ any())
+        verify(controller, times(1))
+            .takeScreenshotFullscreen(
+                eq(topComponent),
+                /* onSavedListener = */ any(),
+                /* requestCallback = */ any()
+            )
+
+        assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
+        val logEvent = eventLogger.get(0)
+
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED UiEvent",
+            logEvent.eventId,
+            SCREENSHOT_REQUESTED_KEY_OTHER.id
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            eventLogger.get(0).packageName
+        )
+    }
+
+    @Test
+    fun takeScreenshotFullscreen_screenshotDataEnabled() {
+        flags.set(SCREENSHOT_METADATA, true)
+
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+                .setTopComponent(topComponent)
+                .build()
+
+        service.handleRequest(request, { /* onSaved */}, callback)
+
+        verify(controller, times(1))
+            .handleScreenshot(
+                eq(ScreenshotData.fromRequest(request)),
+                /* onSavedListener = */ any(),
+                /* requestCallback = */ any()
+            )
 
         assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
         val logEvent = eventLogger.get(0)
 
-        assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
-            logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
-        assertEquals("Expected supplied package name",
-            topComponent.packageName, eventLogger.get(0).packageName)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED UiEvent",
+            logEvent.eventId,
+            SCREENSHOT_REQUESTED_KEY_OTHER.id
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            eventLogger.get(0).packageName
+        )
     }
 
     @Test
     fun takeScreenshotProvidedImage() {
         val bounds = Rect(50, 50, 150, 150)
         val bitmap = makeHardwareBitmap(100, 100)
-        val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
 
-        val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW,
-            bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW)
+                .setTopComponent(topComponent)
+                .setTaskId(TASK_ID)
+                .setUserId(USER_ID)
+                .setBitmap(bitmap)
+                .setBoundsOnScreen(bounds)
+                .setInsets(Insets.NONE)
+                .build()
 
-        service.handleRequest(request, { /* onSaved */ }, callback)
+        service.handleRequest(request, { /* onSaved */}, callback)
 
-        verify(controller, times(1)).handleImageAsScreenshot(
-            argThat { b -> b.equalsHardwareBitmap(bitmap) },
-            eq(bounds),
-            eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent),
-            /* onSavedListener = */ any(), /* requestCallback = */ any())
+        verify(controller, times(1))
+            .handleImageAsScreenshot(
+                argThat { b -> b.equalsHardwareBitmap(bitmap) },
+                eq(bounds),
+                eq(Insets.NONE),
+                eq(TASK_ID),
+                eq(USER_ID),
+                eq(topComponent),
+                /* onSavedListener = */ any(),
+                /* requestCallback = */ any()
+            )
 
-        assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
+        assertEquals("Expected one UiEvent", 1, eventLogger.numLogs())
         val logEvent = eventLogger.get(0)
 
-        assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent",
-            logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id)
-        assertEquals("Expected supplied package name",
-            topComponent.packageName, eventLogger.get(0).packageName)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED_* UiEvent",
+            logEvent.eventId,
+            SCREENSHOT_REQUESTED_OVERVIEW.id
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            eventLogger.get(0).packageName
+        )
     }
 
     @Test
     fun takeScreenshotFullscreen_userLocked() {
+        flags.set(SCREENSHOT_METADATA, true)
+
         whenever(userManager.isUserUnlocked).thenReturn(false)
 
-        val request = ScreenshotRequest(
-            TAKE_SCREENSHOT_FULLSCREEN,
-            SCREENSHOT_KEY_CHORD,
-            topComponent)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+                .setTopComponent(topComponent)
+                .build()
 
-        service.handleRequest(request, { /* onSaved */ }, callback)
+        service.handleRequest(request, { /* onSaved */}, callback)
 
         verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
         verify(callback, times(1)).reportError()
         verifyZeroInteractions(controller)
+
+        assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+        val requestEvent = eventLogger.get(0)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED_* UiEvent",
+            SCREENSHOT_REQUESTED_KEY_OTHER.id,
+            requestEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            requestEvent.packageName
+        )
+        val failureEvent = eventLogger.get(1)
+        assertEquals(
+            "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+            SCREENSHOT_CAPTURE_FAILED.id,
+            failureEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            failureEvent.packageName
+        )
     }
 
     @Test
     fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
-        whenever(devicePolicyManager.getScreenCaptureDisabled(
-            isNull(), eq(UserHandle.USER_ALL))
-        ).thenReturn(true)
+        flags.set(SCREENSHOT_METADATA, true)
 
-        whenever(devicePolicyResourcesManager.getString(
-            eq(SCREENSHOT_BLOCKED_BY_ADMIN),
-            /* Supplier<String> */ any(),
-        )).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+        whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
+            .thenReturn(true)
 
-        val request = ScreenshotRequest(
-            TAKE_SCREENSHOT_FULLSCREEN,
-            SCREENSHOT_KEY_CHORD,
-            topComponent)
+        whenever(
+                devicePolicyResourcesManager.getString(
+                    eq(SCREENSHOT_BLOCKED_BY_ADMIN),
+                    /* Supplier<String> */
+                    any(),
+                )
+            )
+            .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
 
-        service.handleRequest(request, { /* onSaved */ }, callback)
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+                .setTopComponent(topComponent)
+                .build()
+
+        service.handleRequest(request, { /* onSaved */}, callback)
 
         // error shown: Toast.makeText(...).show(), untestable
         verify(callback, times(1)).reportError()
         verifyZeroInteractions(controller)
+        assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+        val requestEvent = eventLogger.get(0)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED_* UiEvent",
+            SCREENSHOT_REQUESTED_KEY_OTHER.id,
+            requestEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            requestEvent.packageName
+        )
+        val failureEvent = eventLogger.get(1)
+        assertEquals(
+            "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+            SCREENSHOT_CAPTURE_FAILED.id,
+            failureEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            failureEvent.packageName
+        )
+    }
+
+    @Test
+    fun takeScreenshotFullscreen_userLocked_metadataDisabled() {
+        flags.set(SCREENSHOT_METADATA, false)
+        whenever(userManager.isUserUnlocked).thenReturn(false)
+
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+                .setTopComponent(topComponent)
+                .build()
+
+        service.handleRequest(request, { /* onSaved */}, callback)
+
+        verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
+        verify(callback, times(1)).reportError()
+        verifyZeroInteractions(controller)
+
+        assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+        val requestEvent = eventLogger.get(0)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED_* UiEvent",
+            SCREENSHOT_REQUESTED_KEY_OTHER.id,
+            requestEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            requestEvent.packageName
+        )
+        val failureEvent = eventLogger.get(1)
+        assertEquals(
+            "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+            SCREENSHOT_CAPTURE_FAILED.id,
+            failureEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            failureEvent.packageName
+        )
+    }
+
+    @Test
+    fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers_metadataDisabled() {
+        flags.set(SCREENSHOT_METADATA, false)
+
+        whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
+            .thenReturn(true)
+
+        whenever(
+                devicePolicyResourcesManager.getString(
+                    eq(SCREENSHOT_BLOCKED_BY_ADMIN),
+                    /* Supplier<String> */
+                    any(),
+                )
+            )
+            .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+                .setTopComponent(topComponent)
+                .build()
+
+        service.handleRequest(request, { /* onSaved */}, callback)
+
+        // error shown: Toast.makeText(...).show(), untestable
+        verify(callback, times(1)).reportError()
+        verifyZeroInteractions(controller)
+        assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+        val requestEvent = eventLogger.get(0)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED_* UiEvent",
+            SCREENSHOT_REQUESTED_KEY_OTHER.id,
+            requestEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            requestEvent.packageName
+        )
+        val failureEvent = eventLogger.get(1)
+        assertEquals(
+            "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+            SCREENSHOT_CAPTURE_FAILED.id,
+            failureEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            failureEvent.packageName
+        )
+    }
+
+    @Test
+    fun takeScreenshot_workProfile_nullBitmap_metadataDisabled() {
+        flags.set(SCREENSHOT_METADATA, false)
+
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+                .setTopComponent(topComponent)
+                .build()
+
+        doThrow(IllegalStateException::class.java)
+            .whenever(requestProcessor)
+            .processAsync(any(ScreenshotRequest::class.java), any())
+
+        service.handleRequest(request, { /* onSaved */}, callback)
+
+        verify(callback, times(1)).reportError()
+        verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
+        verifyZeroInteractions(controller)
+        assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+        val requestEvent = eventLogger.get(0)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED_* UiEvent",
+            SCREENSHOT_REQUESTED_KEY_OTHER.id,
+            requestEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            requestEvent.packageName
+        )
+        val failureEvent = eventLogger.get(1)
+        assertEquals(
+            "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+            SCREENSHOT_CAPTURE_FAILED.id,
+            failureEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            failureEvent.packageName
+        )
+    }
+    @Test
+    fun takeScreenshot_workProfile_nullBitmap() {
+        flags.set(SCREENSHOT_METADATA, true)
+
+        val request =
+            ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
+                .setTopComponent(topComponent)
+                .build()
+
+        doThrow(IllegalStateException::class.java)
+            .whenever(requestProcessor)
+            .processAsync(any(ScreenshotData::class.java), any())
+
+        service.handleRequest(request, { /* onSaved */}, callback)
+
+        verify(callback, times(1)).reportError()
+        verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
+        verifyZeroInteractions(controller)
+        assertEquals("Expected two UiEvents", 2, eventLogger.numLogs())
+        val requestEvent = eventLogger.get(0)
+        assertEquals(
+            "Expected SCREENSHOT_REQUESTED_* UiEvent",
+            SCREENSHOT_REQUESTED_KEY_OTHER.id,
+            requestEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            requestEvent.packageName
+        )
+        val failureEvent = eventLogger.get(1)
+        assertEquals(
+            "Expected SCREENSHOT_CAPTURE_FAILED UiEvent",
+            SCREENSHOT_CAPTURE_FAILED.id,
+            failureEvent.eventId
+        )
+        assertEquals(
+            "Expected supplied package name",
+            topComponent.packageName,
+            failureEvent.packageName
+        )
     }
 }
 
 private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
     return config == HARDWARE &&
-            other.config == HARDWARE &&
-            hardwareBuffer == other.hardwareBuffer &&
-            colorSpace == other.colorSpace
+        other.config == HARDWARE &&
+        hardwareBuffer == other.hardwareBuffer &&
+        colorSpace == other.colorSpace
 }
 
 /** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
 private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
-    val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
-        HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+    val buffer =
+        HardwareBuffer.create(
+            width,
+            height,
+            HardwareBuffer.RGBA_8888,
+            1,
+            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+        )
     return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
index bd04b3c..3440f91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -16,13 +16,11 @@
 
 package com.android.systemui.screenshot;
 
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
@@ -33,28 +31,35 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.FakeSharedPreferences;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import kotlin.Unit;
+
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-public class WorkProfileMessageControllerTest {
+public class WorkProfileMessageControllerTest extends SysuiTestCase {
     private static final String DEFAULT_LABEL = "default label";
-    private static final String BADGED_DEFAULT_LABEL = "badged default label";
     private static final String APP_LABEL = "app label";
-    private static final String BADGED_APP_LABEL = "badged app label";
     private static final UserHandle NON_WORK_USER = UserHandle.of(0);
     private static final UserHandle WORK_USER = UserHandle.of(10);
 
@@ -63,17 +68,13 @@
     @Mock
     private PackageManager mPackageManager;
     @Mock
-    private Context mContext;
-    @Mock
-    private WorkProfileMessageController.WorkProfileMessageDisplay mMessageDisplay;
+    private Context mMockContext;
     @Mock
     private Drawable mActivityIcon;
     @Mock
     private Drawable mBadgedActivityIcon;
     @Mock
     private ActivityInfo mActivityInfo;
-    @Captor
-    private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
 
     private FakeSharedPreferences mSharedPreferences = new FakeSharedPreferences();
 
@@ -84,14 +85,10 @@
         MockitoAnnotations.initMocks(this);
 
         when(mUserManager.isManagedProfile(eq(WORK_USER.getIdentifier()))).thenReturn(true);
-        when(mContext.getSharedPreferences(
+        when(mMockContext.getSharedPreferences(
                 eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
                 eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
-        when(mContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
-        when(mPackageManager.getUserBadgedLabel(eq(DEFAULT_LABEL), any()))
-                .thenReturn(BADGED_DEFAULT_LABEL);
-        when(mPackageManager.getUserBadgedLabel(eq(APP_LABEL), any()))
-                .thenReturn(BADGED_APP_LABEL);
+        when(mMockContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
         when(mPackageManager.getActivityIcon(any(ComponentName.class)))
                 .thenReturn(mActivityIcon);
         when(mPackageManager.getUserBadgedIcon(
@@ -103,16 +100,13 @@
         mSharedPreferences.edit().putBoolean(
                 WorkProfileMessageController.PREFERENCE_KEY, false).apply();
 
-        mMessageController = new WorkProfileMessageController(mContext, mUserManager,
+        mMessageController = new WorkProfileMessageController(mMockContext, mUserManager,
                 mPackageManager);
     }
 
     @Test
     public void testOnScreenshotTaken_notManaged() {
-        mMessageController.onScreenshotTaken(NON_WORK_USER, mMessageDisplay);
-
-        verify(mMessageDisplay, never())
-                .showWorkProfileMessage(any(), nullable(Drawable.class), any());
+        assertNull(mMessageController.onScreenshotTaken(NON_WORK_USER));
     }
 
     @Test
@@ -120,10 +114,7 @@
         mSharedPreferences.edit().putBoolean(
                 WorkProfileMessageController.PREFERENCE_KEY, true).apply();
 
-        mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
-
-        verify(mMessageDisplay, never())
-                .showWorkProfileMessage(any(), nullable(Drawable.class), any());
+        assertNull(mMessageController.onScreenshotTaken(WORK_USER));
     }
 
     @Test
@@ -133,28 +124,45 @@
                 any(PackageManager.ComponentInfoFlags.class))).thenThrow(
                 new PackageManager.NameNotFoundException());
 
-        mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+        WorkProfileMessageController.WorkProfileFirstRunData data =
+                mMessageController.onScreenshotTaken(WORK_USER);
 
-        verify(mMessageDisplay).showWorkProfileMessage(
-                eq(BADGED_DEFAULT_LABEL), eq(null), any());
+        assertEquals(DEFAULT_LABEL, data.getAppName());
+        assertNull(data.getIcon());
     }
 
     @Test
     public void testOnScreenshotTaken() {
-        mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+        WorkProfileMessageController.WorkProfileFirstRunData data =
+                mMessageController.onScreenshotTaken(WORK_USER);
 
-        verify(mMessageDisplay).showWorkProfileMessage(
-                eq(BADGED_APP_LABEL), eq(mBadgedActivityIcon), mRunnableArgumentCaptor.capture());
+        assertEquals(APP_LABEL, data.getAppName());
+        assertEquals(mBadgedActivityIcon, data.getIcon());
+    }
 
-        // Dismiss hasn't been tapped, preference untouched.
-        assertFalse(
-                mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false));
+    @Test
+    public void testPopulateView() throws InterruptedException {
+        ViewGroup layout = (ViewGroup) LayoutInflater.from(mContext).inflate(
+                R.layout.screenshot_work_profile_first_run, null);
+        WorkProfileMessageController.WorkProfileFirstRunData data =
+                new WorkProfileMessageController.WorkProfileFirstRunData(APP_LABEL,
+                        mBadgedActivityIcon);
+        final CountDownLatch countdown = new CountDownLatch(1);
+        mMessageController.populateView(layout, data, () -> {
+            countdown.countDown();
+            return Unit.INSTANCE;
+        });
 
-        mRunnableArgumentCaptor.getValue().run();
+        ImageView image = layout.findViewById(R.id.screenshot_message_icon);
+        assertEquals(mBadgedActivityIcon, image.getDrawable());
+        TextView text = layout.findViewById(R.id.screenshot_message_content);
+        // The app name is used in a template, but at least validate that it was inserted.
+        assertTrue(text.getText().toString().contains(APP_LABEL));
 
-        // After dismiss has been tapped, the setting should be updated.
-        assertTrue(
-                mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false));
+        // Validate that clicking the dismiss button calls back properly.
+        assertEquals(1, countdown.getCount());
+        layout.findViewById(R.id.message_dismiss_button).callOnClick();
+        countdown.await(1000, TimeUnit.MILLISECONDS);
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt
new file mode 100644
index 0000000..ae976a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 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.systemui.settings
+
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.hardware.display.DisplayManagerGlobal
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DisplayTrackerImplTest : SysuiTestCase() {
+    @Mock private lateinit var displayManager: DisplayManager
+    @Mock private lateinit var handler: Handler
+
+    private val executor = Executor(Runnable::run)
+    private lateinit var mDefaultDisplay: Display
+    private lateinit var mSecondaryDisplay: Display
+    private lateinit var tracker: DisplayTrackerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mDefaultDisplay =
+            Display(
+                DisplayManagerGlobal.getInstance(),
+                Display.DEFAULT_DISPLAY,
+                DisplayInfo(),
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+            )
+        mSecondaryDisplay =
+            Display(
+                DisplayManagerGlobal.getInstance(),
+                Display.DEFAULT_DISPLAY + 1,
+                DisplayInfo(),
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+            )
+
+        `when`(displayManager.displays).thenReturn(arrayOf(mDefaultDisplay, mSecondaryDisplay))
+
+        tracker = DisplayTrackerImpl(displayManager, handler)
+    }
+
+    @Test
+    fun testGetDefaultDisplay() {
+        assertThat(tracker.defaultDisplayId).isEqualTo(Display.DEFAULT_DISPLAY)
+    }
+
+    @Test
+    fun testGetAllDisplays() {
+        assertThat(tracker.allDisplays).isEqualTo(arrayOf(mDefaultDisplay, mSecondaryDisplay))
+    }
+
+    @Test
+    fun registerCallback_registersDisplayListener() {
+        tracker.addDisplayChangeCallback(TestCallback(), executor)
+        verify(displayManager).registerDisplayListener(any(), any())
+    }
+
+    @Test
+    fun registerBrightnessCallback_registersDisplayListener() {
+        tracker.addBrightnessChangeCallback(TestCallback(), executor)
+        verify(displayManager)
+            .registerDisplayListener(any(), any(), eq(EVENT_FLAG_DISPLAY_BRIGHTNESS))
+    }
+
+    @Test
+    fun unregisterCallback_displayListenerStillRegistered() {
+        val callback1 = TestCallback()
+        tracker.addDisplayChangeCallback(callback1, executor)
+        tracker.addDisplayChangeCallback(TestCallback(), executor)
+        tracker.removeCallback(callback1)
+
+        verify(displayManager, never()).unregisterDisplayListener(any())
+    }
+
+    @Test
+    fun unregisterLastCallback_unregistersDisplayListener() {
+        val callback = TestCallback()
+        tracker.addDisplayChangeCallback(callback, executor)
+        tracker.removeCallback(callback)
+
+        verify(displayManager).unregisterDisplayListener(any())
+    }
+
+    @Test
+    fun callbackCalledOnDisplayAdd() {
+        val testDisplay = 2
+        val callback = TestCallback()
+        tracker.addDisplayChangeCallback(callback, executor)
+        tracker.displayChangedListener.onDisplayAdded(testDisplay)
+
+        assertThat(callback.lastDisplayAdded).isEqualTo(testDisplay)
+    }
+
+    @Test
+    fun callbackCalledOnDisplayRemoved() {
+        val testDisplay = 2
+        val callback = TestCallback()
+        tracker.addDisplayChangeCallback(callback, executor)
+        tracker.displayChangedListener.onDisplayRemoved(testDisplay)
+
+        assertThat(callback.lastDisplayRemoved).isEqualTo(testDisplay)
+    }
+
+    @Test
+    fun callbackCalledOnDisplayChanged() {
+        val testDisplay = 2
+        val callback = TestCallback()
+        tracker.addDisplayChangeCallback(callback, executor)
+        tracker.displayChangedListener.onDisplayChanged(testDisplay)
+
+        assertThat(callback.lastDisplayChanged).isEqualTo(testDisplay)
+    }
+
+    @Test
+    fun callbackCalledOnBrightnessChanged() {
+        val testDisplay = 2
+        val callback = TestCallback()
+        tracker.addBrightnessChangeCallback(callback, executor)
+        tracker.displayBrightnessChangedListener.onDisplayChanged(testDisplay)
+
+        assertThat(callback.lastDisplayChanged).isEqualTo(testDisplay)
+    }
+
+    private class TestCallback : DisplayTracker.Callback {
+        var lastDisplayAdded = -1
+        var lastDisplayRemoved = -1
+        var lastDisplayChanged = -1
+
+        override fun onDisplayAdded(displayId: Int) {
+            lastDisplayAdded = displayId
+        }
+
+        override fun onDisplayRemoved(displayId: Int) {
+            lastDisplayRemoved = displayId
+        }
+
+        override fun onDisplayChanged(displayId: Int) {
+            lastDisplayChanged = displayId
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
index 3710281..57b6b2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplReceiveTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.settings
 
+import android.app.IActivityManager
 import android.content.Context
 import android.content.Intent
 import android.content.pm.UserInfo
@@ -51,6 +52,7 @@
 
     @Mock private lateinit var context: Context
     @Mock private lateinit var userManager: UserManager
+    @Mock private lateinit var iActivityManager: IActivityManager
     @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
     @Mock(stubOnly = true) private lateinit var handler: Handler
 
@@ -67,7 +69,7 @@
         `when`(context.user).thenReturn(UserHandle.SYSTEM)
         `when`(context.createContextAsUser(ArgumentMatchers.any(), anyInt())).thenReturn(context)
 
-        tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
+        tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index e65bbb1..71ba215 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -16,11 +16,14 @@
 
 package com.android.systemui.settings
 
+import android.app.IActivityManager
+import android.app.IUserSwitchObserver
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
 import android.content.pm.UserInfo
 import android.os.Handler
+import android.os.IRemoteCallback
 import android.os.UserHandle
 import android.os.UserManager
 import android.testing.AndroidTestingRunner
@@ -29,19 +32,20 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.mockito.capture
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.ArgumentMatchers.isNull
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -51,6 +55,10 @@
     private lateinit var context: Context
     @Mock
     private lateinit var userManager: UserManager
+    @Mock
+    private lateinit var iActivityManager: IActivityManager
+    @Mock
+    private lateinit var userSwitchingReply: IRemoteCallback
     @Mock(stubOnly = true)
     private lateinit var dumpManager: DumpManager
     @Mock(stubOnly = true)
@@ -76,7 +84,7 @@
             listOf(info)
         }
 
-        tracker = UserTrackerImpl(context, userManager, dumpManager, handler)
+        tracker = UserTrackerImpl(context, userManager, iActivityManager, dumpManager, handler)
     }
 
     @Test
@@ -125,8 +133,7 @@
         verify(context).registerReceiverForAllUsers(
                 eq(tracker), capture(captor), isNull(), eq(handler))
         with(captor.value) {
-            assertThat(countActions()).isEqualTo(7)
-            assertThat(hasAction(Intent.ACTION_USER_SWITCHED)).isTrue()
+            assertThat(countActions()).isEqualTo(6)
             assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
             assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
             assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)).isTrue()
@@ -158,8 +165,10 @@
         tracker.initialize(0)
         val newID = 5
 
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID)
-        tracker.onReceive(context, intent)
+        val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+        verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+        captor.value.onUserSwitching(newID, userSwitchingReply)
+        verify(userSwitchingReply).sendResult(any())
 
         verify(userManager).getProfiles(newID)
 
@@ -272,6 +281,24 @@
     }
 
     @Test
+    fun testCallbackCalledOnUserChanging() {
+        tracker.initialize(0)
+        val callback = TestCallback()
+        tracker.addCallback(callback, executor)
+
+        val newID = 5
+
+        val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+        verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+        captor.value.onUserSwitching(newID, userSwitchingReply)
+        verify(userSwitchingReply).sendResult(any())
+
+        assertThat(callback.calledOnUserChanging).isEqualTo(1)
+        assertThat(callback.lastUser).isEqualTo(newID)
+        assertThat(callback.lastUserContext?.userId).isEqualTo(newID)
+    }
+
+    @Test
     fun testCallbackCalledOnUserChanged() {
         tracker.initialize(0)
         val callback = TestCallback()
@@ -279,8 +306,9 @@
 
         val newID = 5
 
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, newID)
-        tracker.onReceive(context, intent)
+        val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+        verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+        captor.value.onUserSwitchComplete(newID)
 
         assertThat(callback.calledOnUserChanged).isEqualTo(1)
         assertThat(callback.lastUser).isEqualTo(newID)
@@ -330,25 +358,36 @@
         tracker.addCallback(callback, executor)
         tracker.removeCallback(callback)
 
-        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, 5)
-        tracker.onReceive(context, intent)
+        val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java)
+        verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString())
+        captor.value.onUserSwitching(newID, userSwitchingReply)
+        verify(userSwitchingReply).sendResult(any())
+        captor.value.onUserSwitchComplete(newID)
 
         val intentProfiles = Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
                 .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
 
         tracker.onReceive(context, intentProfiles)
 
+        assertThat(callback.calledOnUserChanging).isEqualTo(0)
         assertThat(callback.calledOnUserChanged).isEqualTo(0)
         assertThat(callback.calledOnProfilesChanged).isEqualTo(0)
     }
 
     private class TestCallback : UserTracker.Callback {
+        var calledOnUserChanging = 0
         var calledOnUserChanged = 0
         var calledOnProfilesChanged = 0
         var lastUser: Int? = null
         var lastUserContext: Context? = null
         var lastUserProfiles = emptyList<UserInfo>()
 
+        override fun onUserChanging(newUser: Int, userContext: Context) {
+            calledOnUserChanging++
+            lastUser = newUser
+            lastUserContext = userContext
+        }
+
         override fun onUserChanged(newUser: Int, userContext: Context) {
             calledOnUserChanged++
             lastUser = newUser
@@ -360,4 +399,4 @@
             lastUserProfiles = profiles
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 9d1802a..58ade49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -28,6 +28,7 @@
 import androidx.test.runner.intercepting.SingleActivityFactory
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeDisplayTracker
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
@@ -51,6 +52,7 @@
     @Mock private lateinit var mainExecutor: Executor
     @Mock private lateinit var backgroundHandler: Handler
     @Mock private lateinit var brightnessSliderController: BrightnessSliderController
+    private val displayTracker = FakeDisplayTracker(mContext)
 
     @Rule
     @JvmField
@@ -60,6 +62,7 @@
                 override fun create(intent: Intent?): TestDialog {
                     return TestDialog(
                         userTracker,
+                        displayTracker,
                         brightnessSliderControllerFactory,
                         mainExecutor,
                         backgroundHandler
@@ -105,12 +108,14 @@
 
     class TestDialog(
         userTracker: UserTracker,
+        displayTracker: FakeDisplayTracker,
         brightnessSliderControllerFactory: BrightnessSliderController.Factory,
         mainExecutor: Executor,
         backgroundHandler: Handler
     ) :
         BrightnessDialog(
             userTracker,
+            displayTracker,
             brightnessSliderControllerFactory,
             mainExecutor,
             backgroundHandler
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index f802a5e..3706859 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -90,7 +90,7 @@
     fun testEdgeElementsAlignedWithEdgeOrGuide_qs() {
         with(qsConstraint) {
             assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
-            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0.5f)
 
             assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
             assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0.5f)
@@ -109,11 +109,12 @@
     @Test
     fun testEdgeElementsAlignedWithEdge_largeScreen() {
         with(largeScreenConstraint) {
-            assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
-            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+            assertThat(getConstraint(R.id.clock).layout.startToEnd).isEqualTo(R.id.begin_guide)
+            assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0.5f)
 
-            assertThat(getConstraint(R.id.privacy_container).layout.endToEnd).isEqualTo(PARENT_ID)
-            assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+            assertThat(getConstraint(R.id.privacy_container).layout.endToStart)
+                .isEqualTo(R.id.end_guide)
+            assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(0.5f)
         }
     }
 
@@ -219,7 +220,12 @@
                 .isEqualTo(cutoutEnd - padding)
         }
 
-        assertThat(changes.largeScreenConstraintsChanges).isNull()
+        with(largeScreenConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+                .isEqualTo(cutoutStart - padding)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+                .isEqualTo(cutoutEnd - padding)
+        }
     }
 
     @Test
@@ -246,7 +252,10 @@
             assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
         }
 
-        assertThat(changes.largeScreenConstraintsChanges).isNull()
+        with(largeScreenConstraint) {
+            assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+            assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+        }
     }
 
     @Test
@@ -333,7 +342,6 @@
                 R.id.clock to "clock",
                 R.id.date to "date",
                 R.id.privacy_container to "privacy",
-                R.id.carrier_group to "carriers",
         )
         views.forEach { (id, name) ->
             assertWithMessage("$name has 0 height in qqs")
@@ -352,7 +360,6 @@
         val views = mapOf(
                 R.id.clock to "clock",
                 R.id.privacy_container to "privacy",
-                R.id.carrier_group to "carriers",
         )
         views.forEach { (id, name) ->
             expect.withMessage("$name changes height")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index f580f5e..91fef1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -692,6 +692,19 @@
         assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
     }
 
+    @Test
+    fun `carrier left padding is set when clock layout changes`() {
+        val width = 200
+        whenever(clock.width).thenReturn(width)
+        whenever(clock.scaleX).thenReturn(2.57f) // 2.57 comes from qs_header.xml
+        val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+
+        verify(clock).addOnLayoutChangeListener(capture(captor))
+        captor.value.onLayoutChange(clock, 0, 0, width, 0, 0, 0, 0, 0)
+
+        verify(carrierGroup).setPaddingRelative(514, 0, 0, 0)
+    }
+
     private fun View.executeLayoutChange(
             left: Int,
             top: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index b568122..2bf2a81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.shade
 
+import android.animation.Animator
 import android.animation.ValueAnimator
 import android.app.StatusBarManager
 import android.content.Context
@@ -45,8 +46,8 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -293,6 +294,34 @@
     }
 
     @Test
+    fun animatorListenersClearedAtEnd() {
+        val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+        whenever(view.animate()).thenReturn(animator)
+
+        mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L)
+        val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
+        verify(animator).setListener(capture(listenerCaptor))
+
+        listenerCaptor.value.onAnimationEnd(mock())
+        verify(animator).setListener(null)
+        verify(animator).setUpdateListener(null)
+    }
+
+    @Test
+    fun animatorListenersClearedOnCancel() {
+        val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
+        whenever(view.animate()).thenReturn(animator)
+
+        mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, 0L)
+        val listenerCaptor = argumentCaptor<Animator.AnimatorListener>()
+        verify(animator).setListener(capture(listenerCaptor))
+
+        listenerCaptor.value.onAnimationCancel(mock())
+        verify(animator).setListener(null)
+        verify(animator).setUpdateListener(null)
+    }
+
+    @Test
     fun demoMode_attachDemoMode() {
         val cb = argumentCaptor<DemoMode>()
         verify(demoModeController).addCallback(capture(cb))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 0f3d4a8..28f7edf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -97,6 +97,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.common.ui.view.LongPressHandlingView;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
@@ -105,10 +106,12 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
@@ -197,7 +200,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class NotificationPanelViewControllerTest extends SysuiTestCase {
 
     private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400;
@@ -302,6 +305,8 @@
     @Mock private GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
 
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    @Mock private KeyguardInteractor mKeyguardInteractor;
+    @Mock private KeyguardLongPressViewModel mKeyuardLongPressViewModel;
     @Mock private CoroutineDispatcher mMainDispatcher;
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock private MotionEvent mDownMotionEvent;
@@ -459,6 +464,9 @@
 
         mMainHandler = new Handler(Looper.getMainLooper());
 
+        when(mView.requireViewById(R.id.keyguard_long_press))
+                .thenReturn(mock(LongPressHandlingView.class));
+
         mNotificationPanelViewController = new NotificationPanelViewController(
                 mView,
                 mMainHandler,
@@ -528,7 +536,9 @@
                 mLockscreenToOccludedTransitionViewModel,
                 mMainDispatcher,
                 mKeyguardTransitionInteractor,
-                mDumpManager);
+                mDumpManager,
+                mKeyuardLongPressViewModel,
+                mKeyguardInteractor);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
                 null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
index bdafc7d..c915502a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
@@ -13,8 +13,11 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.fragments.FragmentService
 import com.android.systemui.navigationbar.NavigationModeController
 import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
 import com.android.systemui.recents.OverviewProxyService
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -29,6 +32,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.eq
@@ -69,6 +73,10 @@
     private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     @Mock
     private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var fragmentService: FragmentService
+    @Mock
+    private lateinit var fragmentHostManager: FragmentHostManager
     @Captor
     lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
     @Captor
@@ -77,6 +85,8 @@
     lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
     @Captor
     lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+    @Captor
+    lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
 
     private lateinit var controller: NotificationsQSContainerController
     private lateinit var navigationModeCallback: ModeChangedListener
@@ -91,8 +101,10 @@
         mContext.ensureTestableResources()
         whenever(notificationsQSContainer.context).thenReturn(mContext)
         whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
+        whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
         fakeSystemClock = FakeSystemClock()
         delayableExecutor = FakeExecutor(fakeSystemClock)
+
         controller = NotificationsQSContainerController(
                 notificationsQSContainer,
                 navigationModeController,
@@ -100,6 +112,7 @@
                 largeScreenShadeHeaderController,
                 shadeExpansionStateManager,
                 featureFlags,
+                fragmentService,
                 delayableExecutor
         )
 
@@ -114,9 +127,10 @@
         doNothing().`when`(notificationsQSContainer)
                 .setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
         doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
-
+        doNothing().`when`(notificationsQSContainer)
+            .addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
         controller.init()
-        controller.onViewAttached()
+        attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer)
 
         navigationModeCallback = navigationModeCaptor.value
         taskbarVisibilityCallback = taskbarVisibilityCaptor.value
@@ -385,6 +399,7 @@
                 largeScreenShadeHeaderController,
                 shadeExpansionStateManager,
                 featureFlags,
+                fragmentService,
                 delayableExecutor
         )
         controller.updateConstraints()
@@ -426,6 +441,17 @@
         verify(largeScreenShadeHeaderController).startCustomizingAnimation(false, 100L)
     }
 
+    @Test
+    fun testTagListenerAdded() {
+        verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer))
+    }
+
+    @Test
+    fun testTagListenerRemoved() {
+        attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer)
+        verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer))
+    }
+
     private fun disableSplitShade() {
         setSplitShadeEnabled(false)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 4c76825..e5d5e3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -45,13 +45,16 @@
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
+import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -102,7 +105,6 @@
     @Mock
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
-    @Mock lateinit var keyguardBouncerContainer: ViewGroup
     @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
     @Mock lateinit var keyguardHostViewController: KeyguardHostViewController
     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -116,6 +118,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(view.bottom).thenReturn(VIEW_BOTTOM)
+        whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container))
+                .thenReturn(mock(ViewGroup::class.java))
+        whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java)))
+                .thenReturn(keyguardBouncerComponent)
+        whenever(keyguardBouncerComponent.keyguardHostViewController)
+                .thenReturn(keyguardHostViewController)
         underTest = NotificationShadeWindowViewController(
             lockscreenShadeTransitionController,
             FalsingCollectorFake(),
@@ -275,6 +283,7 @@
 
     @Test
     fun testGetBouncerContainer() {
+        Mockito.clearInvocations(view)
         underTest.bouncerContainer
         verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index d435624..5cc3ef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -29,9 +29,11 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
+import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardHostViewController;
 import com.android.keyguard.LockIconViewController;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.R;
@@ -94,6 +96,8 @@
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
     @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+    @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent;
+    @Mock private KeyguardHostViewController mKeyguardHostViewController;
     @Mock private NotificationInsetsController mNotificationInsetsController;
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -110,6 +114,12 @@
         when(mView.findViewById(R.id.notification_stack_scroller))
                 .thenReturn(mNotificationStackScrollLayout);
 
+        when(mView.findViewById(R.id.keyguard_bouncer_container)).thenReturn(mock(ViewGroup.class));
+        when(mKeyguardBouncerComponentFactory.create(any(ViewGroup.class))).thenReturn(
+                mKeyguardBouncerComponent);
+        when(mKeyguardBouncerComponent.getKeyguardHostViewController()).thenReturn(
+                mKeyguardHostViewController);
+
         when(mStatusBarStateController.isDozing()).thenReturn(false);
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 3e769e9..76aa08a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.tuner.TunerService.Tunable
@@ -69,6 +70,8 @@
     private lateinit var statusBarStateController: StatusBarStateController
     @Mock
     private lateinit var shadeLogger: ShadeLogger
+    @Mock
+    private lateinit var userTracker: UserTracker
 
     private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
     private lateinit var underTest: PulsingGestureListener
@@ -85,6 +88,7 @@
                 ambientDisplayConfiguration,
                 statusBarStateController,
                 shadeLogger,
+                userTracker,
                 tunerService,
                 dumpManager
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 4d7741ad..78bebb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.plugins.ClockId
 import com.android.systemui.plugins.ClockMetadata
 import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.ClockSettings
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.util.mockito.argumentCaptor
@@ -59,7 +60,7 @@
     private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
     private lateinit var registry: ClockRegistry
 
-    private var settingValue: String = ""
+    private var settingValue: ClockSettings? = null
 
     companion object {
         private fun failFactory(): ClockController {
@@ -79,7 +80,8 @@
         private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
 
         override fun getClocks() = metadata
-        override fun createClock(id: ClockId): ClockController = createCallbacks[id]!!()
+        override fun createClock(settings: ClockSettings): ClockController =
+            createCallbacks[settings.clockId!!]!!()
         override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
 
         fun addClock(
@@ -110,7 +112,7 @@
             userHandle = UserHandle.USER_ALL,
             defaultClockProvider = fakeDefaultProvider
         ) {
-            override var currentClockId: ClockId
+            override var settings: ClockSettings?
                 get() = settingValue
                 set(value) { settingValue = value }
         }
@@ -185,7 +187,7 @@
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
-        settingValue = "clock_3"
+        settingValue = ClockSettings("clock_3", null, null)
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3", { mockClock })
             .addClock("clock_4", "clock 4")
@@ -203,7 +205,7 @@
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
-        settingValue = "clock_3"
+        settingValue = ClockSettings("clock_3", null, null)
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3")
             .addClock("clock_4", "clock 4")
@@ -222,7 +224,7 @@
             .addClock("clock_1", "clock 1")
             .addClock("clock_2", "clock 2")
 
-        settingValue = "clock_3"
+        settingValue = ClockSettings("clock_3", null, null)
         val plugin2 = FakeClockPlugin()
             .addClock("clock_3", "clock 3", { mockClock })
             .addClock("clock_4", "clock 4")
@@ -242,8 +244,8 @@
 
     @Test
     fun jsonDeserialization_gotExpectedObject() {
-        val expected = ClockRegistry.ClockSetting("ID", 500)
-        val actual = ClockRegistry.ClockSetting.deserialize("""{
+        val expected = ClockSettings("ID", null, 500)
+        val actual = ClockSettings.deserialize("""{
             "clockId":"ID",
             "_applied_timestamp":500
         }""")
@@ -252,15 +254,15 @@
 
     @Test
     fun jsonDeserialization_noTimestamp_gotExpectedObject() {
-        val expected = ClockRegistry.ClockSetting("ID", null)
-        val actual = ClockRegistry.ClockSetting.deserialize("{\"clockId\":\"ID\"}")
+        val expected = ClockSettings("ID", null, null)
+        val actual = ClockSettings.deserialize("{\"clockId\":\"ID\"}")
         assertEquals(expected, actual)
     }
 
     @Test
     fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
-        val expected = ClockRegistry.ClockSetting("ID", null)
-        val actual = ClockRegistry.ClockSetting.deserialize("""{
+        val expected = ClockSettings("ID", null, null)
+        val actual = ClockSettings.deserialize("""{
             "clockId":"ID",
             "_applied_timestamp":null
         }""")
@@ -269,22 +271,22 @@
 
     @Test(expected = JSONException::class)
     fun jsonDeserialization_noId_threwException() {
-        val expected = ClockRegistry.ClockSetting("ID", 500)
-        val actual = ClockRegistry.ClockSetting.deserialize("{\"_applied_timestamp\":500}")
+        val expected = ClockSettings("ID", null, 500)
+        val actual = ClockSettings.deserialize("{\"_applied_timestamp\":500}")
         assertEquals(expected, actual)
     }
 
     @Test
     fun jsonSerialization_gotExpectedString() {
         val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}"
-        val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", 500))
+        val actual = ClockSettings.serialize(ClockSettings("ID", null, 500))
         assertEquals(expected, actual)
     }
 
     @Test
     fun jsonSerialization_noTimestamp_gotExpectedString() {
         val expected = "{\"clockId\":\"ID\"}"
-        val actual = ClockRegistry.ClockSetting.serialize( ClockRegistry.ClockSetting("ID", null))
+        val actual = ClockSettings.serialize(ClockSettings("ID", null, null))
         assertEquals(expected, actual)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index a7588dd..cd2efc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -114,7 +114,8 @@
     @Test
     fun defaultClock_events_onTimeTick() {
         val clock = provider.createClock(DEFAULT_CLOCK_ID)
-        clock.events.onTimeTick()
+        clock.smallClock.events.onTimeTick()
+        clock.largeClock.events.onTimeTick()
 
         verify(mockSmallClockView).refreshTime()
         verify(mockLargeClockView).refreshTime()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
index 5a62cc1..ae1c8cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
@@ -1,21 +1,17 @@
 package com.android.systemui.shared.regionsampling
 
-import android.graphics.Rect
+import android.app.WallpaperManager
 import android.testing.AndroidTestingRunner
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper
 import java.io.PrintWriter
 import java.util.concurrent.Executor
-import org.junit.Assert
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
@@ -28,9 +24,8 @@
     @Mock private lateinit var sampledView: View
     @Mock private lateinit var mainExecutor: Executor
     @Mock private lateinit var bgExecutor: Executor
-    @Mock private lateinit var regionSampler: RegionSamplingHelper
     @Mock private lateinit var pw: PrintWriter
-    @Mock private lateinit var callback: RegionSamplingHelper.SamplingCallback
+    @Mock private lateinit var wallpaperManager: WallpaperManager
 
     private lateinit var mRegionSampler: RegionSampler
     private var updateFun: UpdateColorCallback = {}
@@ -38,65 +33,18 @@
     @Before
     fun setUp() {
         whenever(sampledView.isAttachedToWindow).thenReturn(true)
-        whenever(regionSampler.callback).thenReturn(this@RegionSamplerTest.callback)
 
         mRegionSampler =
-            object : RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun) {
-                override fun createRegionSamplingHelper(
-                    sampledView: View,
-                    callback: RegionSamplingHelper.SamplingCallback,
-                    mainExecutor: Executor?,
-                    bgExecutor: Executor?
-                ): RegionSamplingHelper {
-                    return this@RegionSamplerTest.regionSampler
-                }
-            }
+            RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun, wallpaperManager)
     }
 
     @Test
     fun testStartRegionSampler() {
         mRegionSampler.startRegionSampler()
-
-        verify(regionSampler).start(Rect(0, 0, 0, 0))
-    }
-
-    @Test
-    fun testStopRegionSampler() {
-        mRegionSampler.stopRegionSampler()
-
-        verify(regionSampler).stop()
     }
 
     @Test
     fun testDump() {
         mRegionSampler.dump(pw)
-
-        verify(regionSampler).dump(pw)
-    }
-
-    @Test
-    fun testUpdateColorCallback() {
-        regionSampler.callback.onRegionDarknessChanged(false)
-        verify(regionSampler.callback).onRegionDarknessChanged(false)
-        clearInvocations(regionSampler.callback)
-        regionSampler.callback.onRegionDarknessChanged(true)
-        verify(regionSampler.callback).onRegionDarknessChanged(true)
-    }
-
-    @Test
-    fun testFlagFalse() {
-        mRegionSampler =
-            object : RegionSampler(sampledView, mainExecutor, bgExecutor, false, updateFun) {
-                override fun createRegionSamplingHelper(
-                    sampledView: View,
-                    callback: RegionSamplingHelper.SamplingCallback,
-                    mainExecutor: Executor?,
-                    bgExecutor: Executor?
-                ): RegionSamplingHelper {
-                    return this@RegionSamplerTest.regionSampler
-                }
-            }
-
-        Assert.assertEquals(mRegionSampler.regionSampler, null)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
new file mode 100644
index 0000000..5fb1e79
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.systemui.smartspace
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.smartspace.config.BcSmartspaceConfigProvider
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BcSmartspaceConfigProviderTest : SysuiTestCase() {
+    @Mock private lateinit var featureFlags: FeatureFlags
+
+    private lateinit var configProvider: BcSmartspaceConfigProvider
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        configProvider = BcSmartspaceConfigProvider(featureFlags)
+    }
+
+    @Test
+    fun isDefaultDateWeatherDisabled_flagIsTrue_returnsTrue() {
+        whenever(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
+
+        assertTrue(configProvider.isDefaultDateWeatherDisabled)
+    }
+
+    @Test
+    fun isDefaultDateWeatherDisabled_flagIsFalse_returnsFalse() {
+        whenever(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
+
+        assertFalse(configProvider.isDefaultDateWeatherDisabled)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 001e1f4..c5432c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController
+import com.android.systemui.plugins.BcSmartspaceConfigPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.FalsingManager
@@ -94,6 +95,8 @@
     private class TestView(context: Context?) : View(context), SmartspaceView {
         override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {}
 
+        override fun registerConfigProvider(plugin: BcSmartspaceConfigPlugin?) {}
+
         override fun setPrimaryTextColor(color: Int) {}
 
         override fun setIsDreaming(isDreaming: Boolean) {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
index d29e9a6..fa7d869 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt
@@ -20,8 +20,6 @@
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.util.concurrency.Execution
@@ -41,9 +39,6 @@
 @TestableLooper.RunWithLooper
 class LockscreenPreconditionTest : SysuiTestCase() {
     @Mock
-    private lateinit var featureFlags: FeatureFlags
-
-    @Mock
     private lateinit var deviceProvisionedController: DeviceProvisionedController
 
     @Mock
@@ -64,10 +59,7 @@
     fun testFullyEnabled() {
         `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
         `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
-        `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE))
-                .thenReturn(true)
-        val precondition = LockscreenPrecondition(featureFlags, deviceProvisionedController,
-                execution)
+        val precondition = LockscreenPrecondition(deviceProvisionedController, execution)
         precondition.addListener(listener)
 
         `verify`(listener).onCriteriaChanged()
@@ -81,10 +73,8 @@
     fun testProvisioning() {
         `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
         `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
-        `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE))
-                .thenReturn(true)
         val precondition =
-                LockscreenPrecondition(featureFlags, deviceProvisionedController, execution)
+                LockscreenPrecondition(deviceProvisionedController, execution)
         precondition.addListener(listener)
 
         verify(listener).onCriteriaChanged()
@@ -109,10 +99,8 @@
     fun testUserSetup() {
         `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
         `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
-        `when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE))
-                .thenReturn(true)
         val precondition =
-                LockscreenPrecondition(featureFlags, deviceProvisionedController, execution)
+                LockscreenPrecondition(deviceProvisionedController, execution)
         precondition.addListener(listener)
 
         verify(listener).onCriteriaChanged()
@@ -129,4 +117,4 @@
         verify(listener).onCriteriaChanged()
         assertThat(precondition.conditionsMet()).isTrue()
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 8aaa181..e68d3b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -45,6 +45,7 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 
 import org.junit.After;
@@ -62,12 +63,14 @@
     };
 
     private CommandQueue mCommandQueue;
+
+    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
     private Callbacks mCallbacks;
     private static final int SECONDARY_DISPLAY = 1;
 
     @Before
     public void setup() {
-        mCommandQueue = new CommandQueue(mContext);
+        mCommandQueue = new CommandQueue(mContext, mDisplayTracker);
         mCallbacks = mock(Callbacks.class);
         mCommandQueue.addCallback(mCallbacks);
         verify(mCallbacks).disable(anyInt(), eq(0), eq(0), eq(false));
@@ -415,7 +418,7 @@
 
     @Test
     public void testOnDisplayRemoved() {
-        mCommandQueue.onDisplayRemoved(SECONDARY_DISPLAY);
+        mDisplayTracker.triggerOnDisplayRemoved(SECONDARY_DISPLAY);
         waitForIdleSync();
         verify(mCallbacks).onDisplayRemoved(eq(SECONDARY_DISPLAY));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 610bb13..dffa566 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -58,6 +58,7 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.AlarmManager;
 import android.app.Instrumentation;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyResourcesManager;
@@ -183,6 +184,8 @@
     private ScreenLifecycle mScreenLifecycle;
     @Mock
     private AuthController mAuthController;
+    @Mock
+    private AlarmManager mAlarmManager;
     @Captor
     private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
     @Captor
@@ -277,7 +280,9 @@
                 mAuthController, mLockPatternUtils, mScreenLifecycle,
                 mKeyguardBypassController, mAccessibilityManager,
                 mFaceHelpMessageDeferral, mock(KeyguardLogger.class),
-                mAlternateBouncerInteractor);
+                mAlternateBouncerInteractor,
+                mAlarmManager
+        );
         mController.init();
         mController.setIndicationArea(mIndicationArea);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 5124eb9..e6f272b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -37,6 +37,7 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -152,4 +153,18 @@
         // and cause us to drop a frame during the LOCKSCREEN_TRANSITION_FROM_AOD CUJ.
         assertEquals(0.99f, controller.dozeAmount, 0.009f)
     }
+
+    @Test
+    fun testSetDreamState_invokesCallback() {
+        val listener = mock(StatusBarStateController.StateListener::class.java)
+        controller.addCallback(listener)
+
+        controller.setIsDreaming(true)
+        verify(listener).onDreamingChanged(true)
+
+        Mockito.clearInvocations(listener)
+
+        controller.setIsDreaming(false)
+        verify(listener).onDreamingChanged(false)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
index ea06647..a9c3d5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
@@ -6,6 +6,7 @@
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeDisplayTracker
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -17,6 +18,7 @@
 class GenericGestureDetectorTest : SysuiTestCase() {
 
     private lateinit var gestureDetector: TestGestureDetector
+    private val displayTracker = FakeDisplayTracker(mContext)
 
     @Before
     fun setUp() {
@@ -101,12 +103,21 @@
         gestureDetector.addOnGestureDetectedCallback("tag2"){}
 
         gestureDetector.removeOnGestureDetectedCallback("tag")
-        gestureDetector.onInputEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CORRECT_X, 0f, 0))
+        gestureDetector.onInputEvent(
+            MotionEvent.obtain(
+                0,
+                0,
+                MotionEvent.ACTION_DOWN,
+                CORRECT_X,
+                0f,
+                0
+            )
+        )
 
         assertThat(oldCallbackNotified).isFalse()
     }
 
-    inner class TestGestureDetector : GenericGestureDetector("fakeTag") {
+    inner class TestGestureDetector : GenericGestureDetector("fakeTag", displayTracker) {
         var isGestureListening = false
 
         override fun onInputEvent(ev: InputEvent) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index ddcf59e..9c69a6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -32,10 +32,12 @@
 import android.view.View
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.BcSmartspaceConfigPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
@@ -103,6 +105,9 @@
     private lateinit var keyguardBypassController: KeyguardBypassController
 
     @Mock
+    private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Mock
     private lateinit var deviceProvisionedController: DeviceProvisionedController
 
     @Mock
@@ -112,9 +117,18 @@
     private lateinit var handler: Handler
 
     @Mock
+    private lateinit var datePlugin: BcSmartspaceDataPlugin
+
+    @Mock
+    private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+
+    @Mock
     private lateinit var plugin: BcSmartspaceDataPlugin
 
     @Mock
+    private lateinit var configPlugin: BcSmartspaceConfigPlugin
+
+    @Mock
     private lateinit var controllerListener: SmartspaceTargetListener
 
     @Captor
@@ -148,6 +162,8 @@
         KeyguardBypassController.OnBypassStateChangedListener
     private lateinit var deviceProvisionedListener: DeviceProvisionedListener
 
+    private lateinit var dateSmartspaceView: SmartspaceView
+    private lateinit var weatherSmartspaceView: SmartspaceView
     private lateinit var smartspaceView: SmartspaceView
 
     private val clock = FakeSystemClock()
@@ -173,18 +189,24 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE)).thenReturn(true)
+        // Todo(b/261760571): flip the flag value here when feature is launched, and update relevant
+        //  tests.
+        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
 
         `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
                 .thenReturn(fakePrivateLockscreenSettingUri)
         `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
                 .thenReturn(fakeNotifOnLockscreenSettingUri)
         `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
+        `when`(datePlugin.getView(any())).thenReturn(
+                createDateSmartspaceView(), createDateSmartspaceView())
+        `when`(weatherPlugin.getView(any())).thenReturn(
+                createWeatherSmartspaceView(), createWeatherSmartspaceView())
         `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
         `when`(userTracker.userProfiles).thenReturn(userList)
         `when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
-        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
-        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+        `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+        `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
 
         setActiveUser(userHandlePrimary)
         setAllowPrivateNotifications(userHandlePrimary, true)
@@ -205,11 +227,15 @@
                 statusBarStateController,
                 deviceProvisionedController,
                 keyguardBypassController,
+                keyguardUpdateMonitor,
                 execution,
                 executor,
                 bgExecutor,
                 handler,
-                Optional.of(plugin)
+                Optional.of(datePlugin),
+                Optional.of(weatherPlugin),
+                Optional.of(plugin),
+                Optional.of(configPlugin),
         )
 
         verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor))
@@ -217,21 +243,21 @@
     }
 
     @Test(expected = RuntimeException::class)
-    fun testThrowsIfFlagIsDisabled() {
+    fun testBuildAndConnectWeatherView_throwsIfDecouplingDisabled() {
         // GIVEN the feature flag is disabled
-        `when`(featureFlags.isEnabled(Flags.SMARTSPACE)).thenReturn(false)
+        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
 
         // WHEN we try to build the view
-        controller.buildAndConnectView(fakeParent)
+        controller.buildAndConnectWeatherView(fakeParent)
 
         // THEN an exception is thrown
     }
 
     @Test
-    fun connectOnlyAfterDeviceIsProvisioned() {
+    fun testBuildAndConnectView_connectsOnlyAfterDeviceIsProvisioned() {
         // GIVEN an unprovisioned device and an attempt to connect
-        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
-        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
+        `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
+        `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
 
         // WHEN a connection attempt is made and view is attached
         val view = controller.buildAndConnectView(fakeParent)
@@ -241,8 +267,8 @@
         verify(smartspaceManager, never()).createSmartspaceSession(any())
 
         // WHEN it does become provisioned
-        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
-        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+        `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+        `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
         deviceProvisionedListener.onUserSetupChanged()
 
         // THEN the session is created
@@ -252,7 +278,7 @@
     }
 
     @Test
-    fun testListenersAreRegistered() {
+    fun testAddListener_registersListenersForPlugin() {
         // GIVEN a listener is added after a session is created
         connectSession()
 
@@ -261,10 +287,13 @@
 
         // THEN the listener is registered to the underlying plugin
         verify(plugin).registerListener(controllerListener)
+        // The listener is registered only for the plugin, not the date, or weather plugin.
+        verify(datePlugin, never()).registerListener(any())
+        verify(weatherPlugin, never()).registerListener(any())
     }
 
     @Test
-    fun testEarlyRegisteredListenersAreAttachedAfterConnected() {
+    fun testAddListener_earlyRegisteredListenersAreAttachedAfterConnected() {
         // GIVEN a listener that is registered before the session is created
         controller.addListener(controllerListener)
 
@@ -273,10 +302,13 @@
 
         // THEN the listener is subsequently registered
         verify(plugin).registerListener(controllerListener)
+        // The listener is registered only for the plugin, not the date, or the weather plugin.
+        verify(datePlugin, never()).registerListener(any())
+        verify(weatherPlugin, never()).registerListener(any())
     }
 
     @Test
-    fun testEmptyListIsEmittedAndNotifierRemovedAfterDisconnect() {
+    fun testDisconnect_emitsEmptyListAndRemovesNotifier() {
         // GIVEN a registered listener on an active session
         connectSession()
         clearInvocations(plugin)
@@ -288,10 +320,13 @@
         // THEN the listener receives an empty list of targets and unregisters the notifier
         verify(plugin).onTargetsAvailable(emptyList())
         verify(plugin).registerSmartspaceEventNotifier(null)
+        verify(weatherPlugin).onTargetsAvailable(emptyList())
+        verify(weatherPlugin).registerSmartspaceEventNotifier(null)
+        verify(datePlugin).registerSmartspaceEventNotifier(null)
     }
 
     @Test
-    fun testUserChangeReloadsSmartspace() {
+    fun testUserChange_reloadsSmartspace() {
         // GIVEN a connected smartspace session
         connectSession()
 
@@ -303,7 +338,7 @@
     }
 
     @Test
-    fun testSettingsChangeReloadsSmartspace() {
+    fun testSettingsChange_reloadsSmartspace() {
         // GIVEN a connected smartspace session
         connectSession()
 
@@ -315,7 +350,7 @@
     }
 
     @Test
-    fun testThemeChangeUpdatesTextColor() {
+    fun testThemeChange_updatesTextColor() {
         // GIVEN a connected smartspace session
         connectSession()
 
@@ -327,7 +362,23 @@
     }
 
     @Test
-    fun testDozeAmountChangeUpdatesView() {
+    fun testThemeChange_ifDecouplingEnabled_updatesTextColor() {
+        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
+
+        // GIVEN a connected smartspace session
+        connectSession()
+
+        // WHEN the theme changes
+        configChangeListener.onThemeChanged()
+
+        // We update the new text color to match the wallpaper color
+        verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
+        verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
+        verify(smartspaceView).setPrimaryTextColor(anyInt())
+    }
+
+    @Test
+    fun testDozeAmountChange_updatesView() {
         // GIVEN a connected smartspace session
         connectSession()
 
@@ -339,7 +390,23 @@
     }
 
     @Test
-    fun testKeyguardBypassEnabledUpdatesView() {
+    fun testDozeAmountChange_ifDecouplingEnabled_updatesViews() {
+        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
+
+        // GIVEN a connected smartspace session
+        connectSession()
+
+        // WHEN the doze amount changes
+        statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
+
+        // We pass that along to the view
+        verify(dateSmartspaceView).setDozeAmount(0.7f)
+        verify(weatherSmartspaceView).setDozeAmount(0.7f)
+        verify(smartspaceView).setDozeAmount(0.7f)
+    }
+
+    @Test
+    fun testKeyguardBypassEnabled_updatesView() {
         // GIVEN a connected smartspace session
         connectSession()
         `when`(keyguardBypassController.bypassEnabled).thenReturn(true)
@@ -434,6 +501,29 @@
     }
 
     @Test
+    fun testSessionListener_ifDecouplingEnabled_weatherTargetIsFilteredOut() {
+        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
+        connectSession()
+
+        // WHEN we receive a list of targets
+        val targets = listOf(
+                makeTarget(1, userHandlePrimary, isSensitive = true),
+                makeTarget(2, userHandlePrimary),
+                makeTarget(3, userHandleManaged),
+                makeTarget(4, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER)
+        )
+
+        sessionListener.onTargetsAvailable(targets)
+
+        // THEN all non-sensitive content is still shown
+        verify(plugin).onTargetsAvailable(eq(listOf(targets[0], targets[1], targets[2])))
+        // No filtering is applied for the weather plugin
+        verify(weatherPlugin).onTargetsAvailable(eq(targets))
+        // No targets needed for the date plugin
+        verify(datePlugin, never()).onTargetsAvailable(any())
+    }
+
+    @Test
     fun testSettingsAreReloaded() {
         // GIVEN a connected session where the privacy settings later flip to false
         connectSession()
@@ -520,6 +610,17 @@
         verify(smartspaceManager, never()).createSmartspaceSession(any())
         verify(smartspaceView2).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
         verify(smartspaceView2).registerDataProvider(plugin)
+        verify(smartspaceView2).registerConfigProvider(configPlugin)
+    }
+
+    @Test
+    fun testWeatherViewUsesSameSession() {
+        `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
+        // GIVEN a connected session
+        connectSession()
+
+        // No checks is needed here, since connectSession() already checks internally that
+        // createSmartspaceSession is invoked only once.
     }
 
     @Test
@@ -539,8 +640,8 @@
     fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
         // GIVEN an uninitalized smartspaceView
         // WHEN the device is provisioned
-        `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
-        `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+        `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+        `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
         deviceProvisionedListener.onDeviceProvisionedChanged()
 
         // THEN no calls to createSmartspaceSession should occur
@@ -550,17 +651,46 @@
     }
 
     private fun connectSession() {
+        if (controller.isDateWeatherDecoupled()) {
+            val dateView = controller.buildAndConnectDateView(fakeParent)
+            dateSmartspaceView = dateView as SmartspaceView
+            fakeParent.addView(dateView)
+            controller.stateChangeListener.onViewAttachedToWindow(dateView)
+
+            verify(dateSmartspaceView).setUiSurface(
+                    BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+            verify(dateSmartspaceView).registerDataProvider(datePlugin)
+
+            verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
+            verify(dateSmartspaceView).setDozeAmount(0.5f)
+
+            val weatherView = controller.buildAndConnectWeatherView(fakeParent)
+            weatherSmartspaceView = weatherView as SmartspaceView
+            fakeParent.addView(weatherView)
+            controller.stateChangeListener.onViewAttachedToWindow(weatherView)
+
+            verify(weatherSmartspaceView).setUiSurface(
+                    BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+            verify(weatherSmartspaceView).registerDataProvider(weatherPlugin)
+
+            verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
+            verify(weatherSmartspaceView).setDozeAmount(0.5f)
+        }
+
         val view = controller.buildAndConnectView(fakeParent)
         smartspaceView = view as SmartspaceView
-
+        fakeParent.addView(view)
         controller.stateChangeListener.onViewAttachedToWindow(view)
 
         verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
         verify(smartspaceView).registerDataProvider(plugin)
+        verify(smartspaceView).registerConfigProvider(configPlugin)
         verify(smartspaceSession)
                 .addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor))
         sessionListener = sessionListenerCaptor.value
 
+        verify(smartspaceManager).createSmartspaceSession(any())
+
         verify(userTracker).addCallback(capture(userTrackerCaptor), any())
         userListener = userTrackerCaptor.value
 
@@ -585,9 +715,12 @@
 
         verify(smartspaceView).setPrimaryTextColor(anyInt())
         verify(smartspaceView).setDozeAmount(0.5f)
-        clearInvocations(view)
 
-        fakeParent.addView(view)
+        if (controller.isDateWeatherDecoupled()) {
+            clearInvocations(dateSmartspaceView)
+            clearInvocations(weatherSmartspaceView)
+        }
+        clearInvocations(smartspaceView)
     }
 
     private fun setActiveUser(userHandle: UserHandle) {
@@ -633,11 +766,70 @@
         ).thenReturn(if (value) 1 else 0)
     }
 
+    // Separate function for the date view, which implements a specific subset of all functions.
+    private fun createDateSmartspaceView(): SmartspaceView {
+        return spy(object : View(context), SmartspaceView {
+            override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
+            }
+
+            override fun setPrimaryTextColor(color: Int) {
+            }
+
+            override fun setIsDreaming(isDreaming: Boolean) {
+            }
+
+            override fun setUiSurface(uiSurface: String) {
+            }
+
+            override fun setDozeAmount(amount: Float) {
+            }
+
+            override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
+            }
+
+            override fun setFalsingManager(falsingManager: FalsingManager?) {
+            }
+
+            override fun setDnd(image: Drawable?, description: String?) {
+            }
+
+            override fun setNextAlarm(image: Drawable?, description: String?) {
+            }
+        })
+    }
+    // Separate function for the weather view, which implements a specific subset of all functions.
+    private fun createWeatherSmartspaceView(): SmartspaceView {
+        return spy(object : View(context), SmartspaceView {
+            override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
+            }
+
+            override fun setPrimaryTextColor(color: Int) {
+            }
+
+            override fun setIsDreaming(isDreaming: Boolean) {
+            }
+
+            override fun setUiSurface(uiSurface: String) {
+            }
+
+            override fun setDozeAmount(amount: Float) {
+            }
+
+            override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
+            }
+
+            override fun setFalsingManager(falsingManager: FalsingManager?) {
+            }
+        })
+    }
     private fun createSmartspaceView(): SmartspaceView {
         return spy(object : View(context), SmartspaceView {
             override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
             }
 
+            override fun registerConfigProvider(plugin: BcSmartspaceConfigPlugin?) {
+            }
+
             override fun setPrimaryTextColor(color: Int) {
             }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 09f8a10..a869038 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -39,7 +39,6 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
@@ -137,7 +136,6 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         allowTestableLooperAsMainThread();
-        when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true);
 
         mListBuilder = new ShadeListBuilder(
                 mDumpManager,
@@ -1998,29 +1996,7 @@
     }
 
     @Test
-    public void testActiveOrdering_withLegacyStability() {
-        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false);
-        assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change
-        assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X
-        assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change
-        assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X
-        assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG", true); // Z and X + gap
-    }
-
-    @Test
-    public void testStableOrdering_withLegacyStability() {
-        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(false);
-        mStabilityManager.setAllowEntryReordering(false);
-        assertOrder("ABCDEFG", "ABCDEFG", "ABCDEFG", true); // no change
-        assertOrder("ABCDEFG", "ACDEFXBG", "XABCDEFG", false); // X
-        assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG", false); // no change
-        assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG", false); // Z and X
-        assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG", false); // Z and X + gap
-    }
-
-    @Test
     public void testStableOrdering() {
-        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
         mStabilityManager.setAllowEntryReordering(false);
         // No input or output
         assertOrder("", "", "", true);
@@ -2076,7 +2052,6 @@
 
     @Test
     public void testActiveOrdering() {
-        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
         assertOrder("ABCDEFG", "ACDEFXBG", "ACDEFXBG", true); // X
         assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG", true); // no change
         assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG", true); // Z and X
@@ -2133,7 +2108,6 @@
 
     @Test
     public void stableOrderingDisregardedWithSectionChange() {
-        when(mNotifPipelineFlags.isSemiStableSortEnabled()).thenReturn(true);
         // GIVEN the first sectioner's packages can be changed from run-to-run
         List<String> mutableSectionerPackages = new ArrayList<>();
         mutableSectionerPackages.add(PACKAGE_1);
@@ -2229,49 +2203,7 @@
     }
 
     @Test
-    public void groupRevertingToSummaryDoesNotRetainStablePositionWithLegacyIndexLogic() {
-        when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(false);
-
-        // GIVEN a notification group is on screen
-        mStabilityManager.setAllowEntryReordering(false);
-
-        // WHEN the list is originally built with reordering disabled (and section changes allowed)
-        addNotif(0, PACKAGE_1).setRank(2);
-        addNotif(1, PACKAGE_1).setRank(3);
-        addGroupSummary(2, PACKAGE_1, "group").setRank(4);
-        addGroupChild(3, PACKAGE_1, "group").setRank(5);
-        addGroupChild(4, PACKAGE_1, "group").setRank(6);
-        dispatchBuild();
-
-        verifyBuiltList(
-                notif(0),
-                notif(1),
-                group(
-                        summary(2),
-                        child(3),
-                        child(4)
-                )
-        );
-
-        // WHEN the notification summary rank increases and children removed
-        setNewRank(notif(2).entry, 1);
-        mEntrySet.remove(4);
-        mEntrySet.remove(3);
-        dispatchBuild();
-
-        // VERIFY the summary (incorrectly) moves to the top of the section where it is ranked,
-        // despite visual stability being active
-        verifyBuiltList(
-                notif(2),
-                notif(0),
-                notif(1)
-        );
-    }
-
-    @Test
     public void groupRevertingToSummaryRetainsStablePosition() {
-        when(mNotifPipelineFlags.isStabilityIndexFixEnabled()).thenReturn(true);
-
         // GIVEN a notification group is on screen
         mStabilityManager.setAllowEntryReordering(false);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index be6b1dc..2686238 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -321,7 +321,7 @@
         val testDispatcher = UnconfinedTestDispatcher()
         val testScope = TestScope(testDispatcher)
         val fakeSettings = FakeSettings().apply {
-            putBool(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, true)
+            putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
         }
         val seenNotificationsProvider = SeenNotificationsProviderImpl()
         val keyguardCoordinator =
@@ -372,14 +372,14 @@
 
         var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
             get() =
-                fakeSettings.getBoolForUser(
+                fakeSettings.getIntForUser(
                     Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
                     UserHandle.USER_CURRENT,
-                )
+                ) == 1
             set(value) {
-                fakeSettings.putBoolForUser(
+                fakeSettings.putIntForUser(
                     Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                    value,
+                    if (value) 1 else 2,
                     UserHandle.USER_CURRENT,
                 )
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 601771d..03af527 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -41,6 +41,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.PendingIntent;
@@ -59,6 +60,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -107,6 +109,8 @@
     UiEventLoggerFake mUiEventLoggerFake;
     @Mock
     PendingIntent mPendingIntent;
+    @Mock
+    UserTracker mUserTracker;
 
     private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
 
@@ -114,6 +118,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
 
         mUiEventLoggerFake = new UiEventLoggerFake();
 
@@ -131,7 +136,8 @@
                         mMockHandler,
                         mFlags,
                         mKeyguardNotificationVisibilityProvider,
-                        mUiEventLoggerFake);
+                        mUiEventLoggerFake,
+                        mUserTracker);
         mNotifInterruptionStateProvider.mUseHeadsUp = true;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
index 33b94e3..bd03903 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.lang.RuntimeException
 import kotlinx.coroutines.Dispatchers
 import org.junit.Before
 import org.junit.Test
@@ -113,6 +114,24 @@
         assertThat(data).hasSize(2)
     }
 
+    @Test
+    fun onPullAtom_throwsInterruptedException_failsGracefully() {
+        val pipeline: NotifPipeline = mock()
+        whenever(pipeline.allNotifs).thenAnswer { throw InterruptedException("Timeout") }
+        val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
+        assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+            .isEqualTo(StatsManager.PULL_SKIP)
+    }
+
+    @Test
+    fun onPullAtom_throwsRuntimeException_failsGracefully() {
+        val pipeline: NotifPipeline = mock()
+        whenever(pipeline.allNotifs).thenThrow(RuntimeException("Something broke!"))
+        val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
+        assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+            .isEqualTo(StatsManager.PULL_SKIP)
+    }
+
     private fun createLoggerWithNotifications(
         notifications: List<Notification>
     ): NotificationMemoryLogger {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 9d531a1..9e23d54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -44,16 +44,23 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.graphics.Color;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.DisplayMetrics;
 import android.view.View;
+import android.widget.ImageView;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.widget.CachingIconView;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -61,6 +68,7 @@
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 
 import org.junit.Assert;
@@ -72,20 +80,16 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Arrays;
 import java.util.List;
+import java.util.function.Consumer;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 public class ExpandableNotificationRowTest extends SysuiTestCase {
 
-    private ExpandableNotificationRow mGroupRow;
-    private ExpandableNotificationRow mNotifRow;
-    private ExpandableNotificationRow mPublicRow;
-
     private NotificationTestHelper mNotificationTestHelper;
-    boolean mHeadsUpAnimatingAway = false;
-
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
 
     @Before
@@ -96,109 +100,108 @@
                 mDependency,
                 TestableLooper.get(this));
         mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
-        // create a standard private notification row
-        Notification normalNotif = mNotificationTestHelper.createNotification();
-        normalNotif.publicVersion = null;
-        mNotifRow = mNotificationTestHelper.createRow(normalNotif);
+
+        FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
+        fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
+        mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
+    }
+
+    @Test
+    public void testUpdateBackgroundColors_isRecursive() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        group.setTintColor(Color.RED);
+        group.getChildNotificationAt(0).setTintColor(Color.GREEN);
+        group.getChildNotificationAt(1).setTintColor(Color.BLUE);
+
+        assertThat(group.getCurrentBackgroundTint()).isEqualTo(Color.RED);
+        assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint())
+                .isEqualTo(Color.GREEN);
+        assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint())
+                .isEqualTo(Color.BLUE);
+
+        group.updateBackgroundColors();
+
+        int resetTint = group.getCurrentBackgroundTint();
+        assertThat(resetTint).isNotEqualTo(Color.RED);
+        assertThat(group.getChildNotificationAt(0).getCurrentBackgroundTint())
+                .isEqualTo(resetTint);
+        assertThat(group.getChildNotificationAt(1).getCurrentBackgroundTint())
+                .isEqualTo(resetTint);
+    }
+
+    @Test
+    public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
+        // GIVEN a sensitive notification row that's currently redacted
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        measureAndLayout(row);
+        row.setHideSensitiveForIntrinsicHeight(true);
+        row.setSensitive(true, true);
+        assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPublicLayout());
+        assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
+
+        // GIVEN that the row has a height change listener
+        OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
+        row.setOnHeightChangedListener(listener);
+
+        // WHEN the row is set to no longer be sensitive
+        row.setSensitive(false, true);
+
+        // VERIFY that the height change listener is invoked
+        assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
+        assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
+        verify(listener).onHeightChanged(eq(row), eq(false));
+    }
+
+    @Test
+    public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() throws Exception {
+        // GIVEN a sensitive group row that's currently redacted
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        measureAndLayout(group);
+        group.setHideSensitiveForIntrinsicHeight(true);
+        group.setSensitive(true, true);
+        assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPublicLayout());
+        assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
+
+        // GIVEN that the row has a height change listener
+        OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
+        group.setOnHeightChangedListener(listener);
+
+        // WHEN the row is set to no longer be sensitive
+        group.setSensitive(false, true);
+
+        // VERIFY that the height change listener is invoked
+        assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
+        assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
+        verify(listener).onHeightChanged(eq(group), eq(false));
+    }
+
+    @Test
+    public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() throws Exception {
         // create a notification row whose public version is identical
         Notification publicNotif = mNotificationTestHelper.createNotification();
         publicNotif.publicVersion = mNotificationTestHelper.createNotification();
-        mPublicRow = mNotificationTestHelper.createRow(publicNotif);
-        // create a group row
-        mGroupRow = mNotificationTestHelper.createGroup();
-        mGroupRow.setHeadsUpAnimatingAwayListener(
-                animatingAway -> mHeadsUpAnimatingAway = animatingAway);
+        ExpandableNotificationRow publicRow = mNotificationTestHelper.createRow(publicNotif);
 
-    }
-
-    @Test
-    public void testUpdateBackgroundColors_isRecursive() {
-        mGroupRow.setTintColor(Color.RED);
-        mGroupRow.getChildNotificationAt(0).setTintColor(Color.GREEN);
-        mGroupRow.getChildNotificationAt(1).setTintColor(Color.BLUE);
-
-        assertThat(mGroupRow.getCurrentBackgroundTint()).isEqualTo(Color.RED);
-        assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint())
-                .isEqualTo(Color.GREEN);
-        assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint())
-                .isEqualTo(Color.BLUE);
-
-        mGroupRow.updateBackgroundColors();
-
-        int resetTint = mGroupRow.getCurrentBackgroundTint();
-        assertThat(resetTint).isNotEqualTo(Color.RED);
-        assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint())
-                .isEqualTo(resetTint);
-        assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint())
-                .isEqualTo(resetTint);
-    }
-
-    @Test
-    public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws InterruptedException {
-        // GIVEN a sensitive notification row that's currently redacted
-        measureAndLayout(mNotifRow);
-        mNotifRow.setHideSensitiveForIntrinsicHeight(true);
-        mNotifRow.setSensitive(true, true);
-        assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPublicLayout());
-        assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0);
-
-        // GIVEN that the row has a height change listener
-        OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
-        mNotifRow.setOnHeightChangedListener(listener);
-
-        // WHEN the row is set to no longer be sensitive
-        mNotifRow.setSensitive(false, true);
-
-        // VERIFY that the height change listener is invoked
-        assertThat(mNotifRow.getShowingLayout()).isSameInstanceAs(mNotifRow.getPrivateLayout());
-        assertThat(mNotifRow.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(mNotifRow), eq(false));
-    }
-
-    @Test
-    public void testSetSensitiveOnGroupRowNotifiesOfHeightChange() {
-        // GIVEN a sensitive group row that's currently redacted
-        measureAndLayout(mGroupRow);
-        mGroupRow.setHideSensitiveForIntrinsicHeight(true);
-        mGroupRow.setSensitive(true, true);
-        assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPublicLayout());
-        assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0);
-
-        // GIVEN that the row has a height change listener
-        OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
-        mGroupRow.setOnHeightChangedListener(listener);
-
-        // WHEN the row is set to no longer be sensitive
-        mGroupRow.setSensitive(false, true);
-
-        // VERIFY that the height change listener is invoked
-        assertThat(mGroupRow.getShowingLayout()).isSameInstanceAs(mGroupRow.getPrivateLayout());
-        assertThat(mGroupRow.getIntrinsicHeight()).isGreaterThan(0);
-        verify(listener).onHeightChanged(eq(mGroupRow), eq(false));
-    }
-
-    @Test
-    public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange() {
         // GIVEN a sensitive public row that's currently redacted
-        measureAndLayout(mPublicRow);
-        mPublicRow.setHideSensitiveForIntrinsicHeight(true);
-        mPublicRow.setSensitive(true, true);
-        assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPublicLayout());
-        assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0);
+        measureAndLayout(publicRow);
+        publicRow.setHideSensitiveForIntrinsicHeight(true);
+        publicRow.setSensitive(true, true);
+        assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPublicLayout());
+        assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
 
         // GIVEN that the row has a height change listener
         OnHeightChangedListener listener = mock(OnHeightChangedListener.class);
-        mPublicRow.setOnHeightChangedListener(listener);
+        publicRow.setOnHeightChangedListener(listener);
 
         // WHEN the row is set to no longer be sensitive
-        mPublicRow.setSensitive(false, true);
+        publicRow.setSensitive(false, true);
 
         // VERIFY that the height change listener is not invoked, because the height didn't change
-        assertThat(mPublicRow.getShowingLayout()).isSameInstanceAs(mPublicRow.getPrivateLayout());
-        assertThat(mPublicRow.getIntrinsicHeight()).isGreaterThan(0);
-        assertThat(mPublicRow.getPrivateLayout().getMinHeight())
-                .isEqualTo(mPublicRow.getPublicLayout().getMinHeight());
-        verify(listener, never()).onHeightChanged(eq(mPublicRow), eq(false));
+        assertThat(publicRow.getShowingLayout()).isSameInstanceAs(publicRow.getPrivateLayout());
+        assertThat(publicRow.getIntrinsicHeight()).isGreaterThan(0);
+        assertThat(publicRow.getPrivateLayout().getMinHeight())
+                .isEqualTo(publicRow.getPublicLayout().getMinHeight());
+        verify(listener, never()).onHeightChanged(eq(publicRow), eq(false));
     }
 
     private void measureAndLayout(ExpandableNotificationRow row) {
@@ -215,36 +218,43 @@
     }
 
     @Test
-    public void testGroupSummaryNotShowingIconWhenPublic() {
-        mGroupRow.setSensitive(true, true);
-        mGroupRow.setHideSensitiveForIntrinsicHeight(true);
-        assertTrue(mGroupRow.isSummaryWithChildren());
-        assertFalse(mGroupRow.isShowingIcon());
+    public void testGroupSummaryNotShowingIconWhenPublic() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        group.setSensitive(true, true);
+        group.setHideSensitiveForIntrinsicHeight(true);
+        assertTrue(group.isSummaryWithChildren());
+        assertFalse(group.isShowingIcon());
     }
 
     @Test
-    public void testNotificationHeaderVisibleWhenAnimating() {
-        mGroupRow.setSensitive(true, true);
-        mGroupRow.setHideSensitive(true, false, 0, 0);
-        mGroupRow.setHideSensitive(false, true, 0, 0);
-        assertEquals(View.VISIBLE, mGroupRow.getChildrenContainer().getVisibleWrapper()
+    public void testNotificationHeaderVisibleWhenAnimating() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        group.setSensitive(true, true);
+        group.setHideSensitive(true, false, 0, 0);
+        group.setHideSensitive(false, true, 0, 0);
+        assertEquals(View.VISIBLE, group.getChildrenContainer().getVisibleWrapper()
                 .getNotificationHeader().getVisibility());
     }
 
     @Test
-    public void testUserLockedResetEvenWhenNoChildren() {
-        mGroupRow.setUserLocked(true);
-        mGroupRow.setUserLocked(false);
+    public void testUserLockedResetEvenWhenNoChildren() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        group.setUserLocked(true);
+        group.setUserLocked(false);
         assertFalse("The childrencontainer should not be userlocked but is, the state "
-                + "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked());
+                + "seems out of sync.", group.getChildrenContainer().isUserLocked());
     }
 
     @Test
-    public void testReinflatedOnDensityChange() {
+    public void testReinflatedOnDensityChange() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
-        mNotifRow.setChildrenContainer(mockContainer);
+        row.setChildrenContainer(mockContainer);
 
-        mNotifRow.onDensityOrFontScaleChanged();
+        row.onDensityOrFontScaleChanged();
 
         verify(mockContainer).reInflateViews(any(), any());
     }
@@ -287,64 +297,73 @@
     @Test
     public void testAboveShelfChangedListenerCalledWhenGoingBelow() throws Exception {
         ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-        row.setHeadsUp(true);
         AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
         row.setAboveShelfChangedListener(listener);
+        Mockito.reset(listener);
+        row.setHeadsUp(true);
         row.setAboveShelf(false);
         verify(listener).onAboveShelfStateChanged(false);
     }
 
     @Test
     public void testClickSound() throws Exception {
-        assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled());
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        assertTrue("Should play sounds by default.", group.isSoundEffectsEnabled());
         StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController();
         when(mock.isDozing()).thenReturn(true);
-        mGroupRow.setSecureStateProvider(()-> false);
+        group.setSecureStateProvider(()-> false);
         assertFalse("Shouldn't play sounds when dark and trusted.",
-                mGroupRow.isSoundEffectsEnabled());
-        mGroupRow.setSecureStateProvider(()-> true);
+                group.isSoundEffectsEnabled());
+        group.setSecureStateProvider(()-> true);
         assertTrue("Should always play sounds when not trusted.",
-                mGroupRow.isSoundEffectsEnabled());
+                group.isSoundEffectsEnabled());
     }
 
     @Test
-    public void testSetDismissed_longPressListenerRemoved() {
+    public void testSetDismissed_longPressListenerRemoved() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
         ExpandableNotificationRow.LongPressListener listener =
                 mock(ExpandableNotificationRow.LongPressListener.class);
-        mGroupRow.setLongPressListener(listener);
-        mGroupRow.doLongClickCallback(0,0);
-        verify(listener, times(1)).onLongPress(eq(mGroupRow), eq(0), eq(0),
+        group.setLongPressListener(listener);
+        group.doLongClickCallback(0, 0);
+        verify(listener, times(1)).onLongPress(eq(group), eq(0), eq(0),
                 any(NotificationMenuRowPlugin.MenuItem.class));
         reset(listener);
 
-        mGroupRow.dismiss(true);
-        mGroupRow.doLongClickCallback(0,0);
-        verify(listener, times(0)).onLongPress(eq(mGroupRow), eq(0), eq(0),
+        group.dismiss(true);
+        group.doLongClickCallback(0, 0);
+        verify(listener, times(0)).onLongPress(eq(group), eq(0), eq(0),
                 any(NotificationMenuRowPlugin.MenuItem.class));
     }
 
     @Test
-    public void testFeedback_noHeader() {
+    public void testFeedback_noHeader() throws Exception {
+        ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
+
         // public notification is custom layout - no header
-        mGroupRow.setSensitive(true, true);
-        mGroupRow.setOnFeedbackClickListener(null);
-        mGroupRow.setFeedbackIcon(null);
+        groupRow.setSensitive(true, true);
+        groupRow.setOnFeedbackClickListener(null);
+        groupRow.setFeedbackIcon(null);
     }
 
     @Test
-    public void testFeedback_header() {
+    public void testFeedback_header() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
         NotificationContentView publicLayout = mock(NotificationContentView.class);
-        mGroupRow.setPublicLayout(publicLayout);
+        group.setPublicLayout(publicLayout);
         NotificationContentView privateLayout = mock(NotificationContentView.class);
-        mGroupRow.setPrivateLayout(privateLayout);
+        group.setPrivateLayout(privateLayout);
         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
         when(mockContainer.getNotificationChildCount()).thenReturn(1);
-        mGroupRow.setChildrenContainer(mockContainer);
+        group.setChildrenContainer(mockContainer);
 
         final boolean show = true;
         final FeedbackIcon icon = new FeedbackIcon(
                 R.drawable.ic_feedback_alerted, R.string.notification_feedback_indicator_alerted);
-        mGroupRow.setFeedbackIcon(icon);
+        group.setFeedbackIcon(icon);
 
         verify(mockContainer, times(1)).setFeedbackIcon(icon);
         verify(privateLayout, times(1)).setFeedbackIcon(icon);
@@ -352,43 +371,60 @@
     }
 
     @Test
-    public void testFeedbackOnClick() {
+    public void testFeedbackOnClick() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
         ExpandableNotificationRow.CoordinateOnClickListener l = mock(
                 ExpandableNotificationRow.CoordinateOnClickListener.class);
         View view = mock(View.class);
 
-        mGroupRow.setOnFeedbackClickListener(l);
+        group.setOnFeedbackClickListener(l);
 
-        mGroupRow.getFeedbackOnClickListener().onClick(view);
+        group.getFeedbackOnClickListener().onClick(view);
         verify(l, times(1)).onClick(any(), anyInt(), anyInt(), any());
     }
 
     @Test
-    public void testHeadsUpAnimatingAwayListener() {
-        mGroupRow.setHeadsUpAnimatingAway(true);
-        Assert.assertEquals(true, mHeadsUpAnimatingAway);
-        mGroupRow.setHeadsUpAnimatingAway(false);
-        Assert.assertEquals(false, mHeadsUpAnimatingAway);
+    public void testHeadsUpAnimatingAwayListener() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        Consumer<Boolean> headsUpListener = mock(Consumer.class);
+        AboveShelfChangedListener aboveShelfChangedListener = mock(AboveShelfChangedListener.class);
+        group.setHeadsUpAnimatingAwayListener(headsUpListener);
+        group.setAboveShelfChangedListener(aboveShelfChangedListener);
+
+        group.setHeadsUpAnimatingAway(true);
+        verify(headsUpListener).accept(true);
+        verify(aboveShelfChangedListener).onAboveShelfStateChanged(true);
+
+        group.setHeadsUpAnimatingAway(false);
+        verify(headsUpListener).accept(false);
+        verify(aboveShelfChangedListener).onAboveShelfStateChanged(false);
     }
 
     @Test
-    public void testIsBlockingHelperShowing_isCorrectlyUpdated() {
-        mGroupRow.setBlockingHelperShowing(true);
-        assertTrue(mGroupRow.isBlockingHelperShowing());
+    public void testIsBlockingHelperShowing_isCorrectlyUpdated() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
 
-        mGroupRow.setBlockingHelperShowing(false);
-        assertFalse(mGroupRow.isBlockingHelperShowing());
+        group.setBlockingHelperShowing(true);
+        assertTrue(group.isBlockingHelperShowing());
+
+        group.setBlockingHelperShowing(false);
+        assertFalse(group.isBlockingHelperShowing());
     }
 
     @Test
-    public void testGetNumUniqueChildren_defaultChannel() {
-        assertEquals(1, mGroupRow.getNumUniqueChannels());
+    public void testGetNumUniqueChildren_defaultChannel() throws Exception {
+        ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
+
+        assertEquals(1, groupRow.getNumUniqueChannels());
     }
 
     @Test
-    public void testGetNumUniqueChildren_multiChannel() {
+    public void testGetNumUniqueChildren_multiChannel() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
         List<ExpandableNotificationRow> childRows =
-                mGroupRow.getChildrenContainer().getAttachedChildren();
+                group.getChildrenContainer().getAttachedChildren();
         // Give each child a unique channel id/name.
         int i = 0;
         for (ExpandableNotificationRow childRow : childRows) {
@@ -400,25 +436,29 @@
             i++;
         }
 
-        assertEquals(3, mGroupRow.getNumUniqueChannels());
+        assertEquals(3, group.getNumUniqueChannels());
     }
 
     @Test
     public void testIconScrollXAfterTranslationAndReset() throws Exception {
-        mGroupRow.setDismissUsingRowTranslationX(false);
-        mGroupRow.setTranslation(50);
-        assertEquals(50, -mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
 
-        mGroupRow.resetTranslation();
-        assertEquals(0, mGroupRow.getEntry().getIcons().getShelfIcon().getScrollX());
+        group.setDismissUsingRowTranslationX(false);
+        group.setTranslation(50);
+        assertEquals(50, -group.getEntry().getIcons().getShelfIcon().getScrollX());
+
+        group.resetTranslation();
+        assertEquals(0, group.getEntry().getIcons().getShelfIcon().getScrollX());
     }
 
     @Test
-    public void testIsExpanded_userExpanded() {
-        mGroupRow.setExpandable(true);
-        Assert.assertFalse(mGroupRow.isExpanded());
-        mGroupRow.setUserExpanded(true);
-        Assert.assertTrue(mGroupRow.isExpanded());
+    public void testIsExpanded_userExpanded() throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+
+        group.setExpandable(true);
+        Assert.assertFalse(group.isExpanded());
+        group.setUserExpanded(true);
+        Assert.assertTrue(group.isExpanded());
     }
 
     @Test
@@ -537,26 +577,155 @@
     }
 
     @Test
-    public void applyRoundnessAndInv_should_be_immediately_applied_on_childrenContainer_legacy() {
-        mGroupRow.useRoundnessSourceTypes(false);
-        Assert.assertEquals(0f, mGroupRow.getBottomRoundness(), 0.001f);
-        Assert.assertEquals(0f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+    public void applyRoundnessAndInv_should_be_immediately_applied_on_childrenContainer_legacy()
+            throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        group.useRoundnessSourceTypes(false);
+        Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f);
+        Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
 
-        mGroupRow.requestBottomRoundness(1f, SourceType.from(""), false);
+        group.requestBottomRoundness(1f, SourceType.from(""), false);
 
-        Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
-        Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+        Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f);
+        Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
     }
 
     @Test
-    public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer() {
-        mGroupRow.useRoundnessSourceTypes(true);
-        Assert.assertEquals(0f, mGroupRow.getBottomRoundness(), 0.001f);
-        Assert.assertEquals(0f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+    public void applyRoundnessAndInvalidate_should_be_immediately_applied_on_childrenContainer()
+            throws Exception {
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        group.useRoundnessSourceTypes(true);
+        Assert.assertEquals(0f, group.getBottomRoundness(), 0.001f);
+        Assert.assertEquals(0f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
 
-        mGroupRow.requestBottomRoundness(1f, SourceType.from(""), false);
+        group.requestBottomRoundness(1f, SourceType.from(""), false);
 
-        Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
-        Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
+        Assert.assertEquals(1f, group.getBottomRoundness(), 0.001f);
+        Assert.assertEquals(1f, group.getChildrenContainer().getBottomRoundness(), 0.001f);
+    }
+
+    @Test
+    public void testSetContentAnimationRunning_Run() throws Exception {
+        // Create views for the notification row.
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        NotificationContentView publicLayout = mock(NotificationContentView.class);
+        row.setPublicLayout(publicLayout);
+        NotificationContentView privateLayout = mock(NotificationContentView.class);
+        row.setPrivateLayout(privateLayout);
+
+        row.setAnimationRunning(true);
+        verify(publicLayout, times(1)).setContentAnimationRunning(true);
+        verify(privateLayout, times(1)).setContentAnimationRunning(true);
+    }
+
+    @Test
+    public void testSetContentAnimationRunning_Stop() throws Exception {
+        // Create views for the notification row.
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        NotificationContentView publicLayout = mock(NotificationContentView.class);
+        row.setPublicLayout(publicLayout);
+        NotificationContentView privateLayout = mock(NotificationContentView.class);
+        row.setPrivateLayout(privateLayout);
+
+        row.setAnimationRunning(false);
+        verify(publicLayout, times(1)).setContentAnimationRunning(false);
+        verify(privateLayout, times(1)).setContentAnimationRunning(false);
+    }
+
+    @Test
+    public void testSetContentAnimationRunningInGroupChild_Run() throws Exception {
+        // Creates parent views on groupRow.
+        ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
+        NotificationContentView publicParentLayout = mock(NotificationContentView.class);
+        groupRow.setPublicLayout(publicParentLayout);
+        NotificationContentView privateParentLayout = mock(NotificationContentView.class);
+        groupRow.setPrivateLayout(privateParentLayout);
+
+        // Create child views on row.
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+        NotificationContentView publicChildLayout = mock(NotificationContentView.class);
+        row.setPublicLayout(publicChildLayout);
+        NotificationContentView privateChildLayout = mock(NotificationContentView.class);
+        row.setPrivateLayout(privateChildLayout);
+        when(row.isGroupExpanded()).thenReturn(true);
+        setMockChildrenContainer(groupRow, row);
+
+        groupRow.setAnimationRunning(true);
+        verify(publicParentLayout, times(1)).setContentAnimationRunning(true);
+        verify(privateParentLayout, times(1)).setContentAnimationRunning(true);
+        // The child layouts should be started too.
+        verify(publicChildLayout, times(1)).setContentAnimationRunning(true);
+        verify(privateChildLayout, times(1)).setContentAnimationRunning(true);
+    }
+
+
+    @Test
+    public void testSetIconAnimationRunningGroup_Run() throws Exception {
+        // Create views for a group row.
+        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
+        ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+        NotificationContentView publicParentLayout = mock(NotificationContentView.class);
+        group.setPublicLayout(publicParentLayout);
+        NotificationContentView privateParentLayout = mock(NotificationContentView.class);
+        group.setPrivateLayout(privateParentLayout);
+        when(group.isGroupExpanded()).thenReturn(true);
+
+        // Add the child to the group.
+        NotificationContentView publicChildLayout = mock(NotificationContentView.class);
+        child.setPublicLayout(publicChildLayout);
+        NotificationContentView privateChildLayout = mock(NotificationContentView.class);
+        child.setPrivateLayout(privateChildLayout);
+        when(child.isGroupExpanded()).thenReturn(true);
+
+        NotificationChildrenContainer mockContainer =
+                setMockChildrenContainer(group, child);
+
+        // Mock the children view wrappers, and give them each an icon.
+        NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class);
+        when(mockContainer.getNotificationViewWrapper()).thenReturn(mockViewWrapper);
+        CachingIconView mockIcon = mock(CachingIconView.class);
+        when(mockViewWrapper.getIcon()).thenReturn(mockIcon);
+
+        NotificationViewWrapper mockLowPriorityViewWrapper = mock(NotificationViewWrapper.class);
+        when(mockContainer.getLowPriorityViewWrapper()).thenReturn(mockLowPriorityViewWrapper);
+        CachingIconView mockLowPriorityIcon = mock(CachingIconView.class);
+        when(mockLowPriorityViewWrapper.getIcon()).thenReturn(mockLowPriorityIcon);
+
+        // Give the icon image views drawables, so we can make sure they animate.
+        // We use both AnimationDrawables and AnimatedVectorDrawables to ensure both work.
+        AnimationDrawable drawable = mock(AnimationDrawable.class);
+        AnimatedVectorDrawable vectorDrawable = mock(AnimatedVectorDrawable.class);
+        setDrawableIconsInImageView(mockIcon, drawable, vectorDrawable);
+
+        AnimationDrawable lowPriDrawable = mock(AnimationDrawable.class);
+        AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class);
+        setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable);
+
+        group.setAnimationRunning(true);
+        verify(drawable, times(1)).start();
+        verify(vectorDrawable, times(1)).start();
+        verify(lowPriDrawable, times(1)).start();
+        verify(lowPriVectorDrawable, times(1)).start();
+    }
+
+    private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
+            Drawable rightIconDrawable) {
+        ImageView iconView = mock(ImageView.class);
+        when(icon.findViewById(com.android.internal.R.id.icon)).thenReturn(iconView);
+        when(iconView.getDrawable()).thenReturn(iconDrawable);
+
+        ImageView rightIconView = mock(ImageView.class);
+        when(icon.findViewById(com.android.internal.R.id.right_icon)).thenReturn(rightIconView);
+        when(rightIconView.getDrawable()).thenReturn(rightIconDrawable);
+    }
+
+    private NotificationChildrenContainer setMockChildrenContainer(
+            ExpandableNotificationRow parentRow, ExpandableNotificationRow childRow) {
+        List<ExpandableNotificationRow> rowList = Arrays.asList(childRow);
+        NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
+        when(mockContainer.getNotificationChildCount()).thenReturn(1);
+        when(mockContainer.getAttachedChildren()).thenReturn(rowList);
+        parentRow.setChildrenContainer(mockContainer);
+        return mockContainer;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
index 1f92b0a..819a75b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
@@ -98,5 +100,16 @@
 
         mView.setSecondaryVisible(true /* visible */, true /* animate */);
     }
+
+    @Test
+    public void testSetFooterLabelTextAndIcon() {
+        mView.setFooterLabelTextAndIcon(
+                R.string.unlock_to_see_notif_text,
+                R.drawable.ic_friction_lock_closed);
+        assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
+        assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.GONE);
+        assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
+                .isEqualTo(View.VISIBLE);
+    }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 562b4df..7b2051d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -34,9 +34,12 @@
 import com.android.systemui.statusbar.notification.FeedbackIcon
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,6 +47,7 @@
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations.initMocks
 
@@ -305,6 +309,86 @@
         assertEquals(0, getMarginBottom(actionListMarginTarget))
     }
 
+    @Test
+    fun onSetAnimationRunning() {
+        // Given: contractedWrapper, enpandedWrapper, and headsUpWrapper being set
+        val mockContracted = mock<NotificationViewWrapper>()
+        val mockExpanded = mock<NotificationViewWrapper>()
+        val mockHeadsUp = mock<NotificationViewWrapper>()
+
+        view.setContractedWrapper(mockContracted)
+        view.setExpandedWrapper(mockExpanded)
+        view.setHeadsUpWrapper(mockHeadsUp)
+
+        // When: we set content animation running.
+        assertTrue(view.setContentAnimationRunning(true))
+
+        // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
+        // called on them.
+        verify(mockContracted, times(1)).setAnimationsRunning(true)
+        verify(mockExpanded, times(1)).setAnimationsRunning(true)
+        verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+
+        // When: we set content animation running true _again_.
+        assertFalse(view.setContentAnimationRunning(true))
+
+        // Then: the children should not have setAnimationRunning called on them again.
+        // Verify counts number of calls so far on the object, so these still register as 1.
+        verify(mockContracted, times(1)).setAnimationsRunning(true)
+        verify(mockExpanded, times(1)).setAnimationsRunning(true)
+        verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+    }
+
+    @Test
+    fun onSetAnimationStopped() {
+        // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
+        val mockContracted = mock<NotificationViewWrapper>()
+        val mockExpanded = mock<NotificationViewWrapper>()
+        val mockHeadsUp = mock<NotificationViewWrapper>()
+
+        view.setContractedWrapper(mockContracted)
+        view.setExpandedWrapper(mockExpanded)
+        view.setHeadsUpWrapper(mockHeadsUp)
+
+        // When: we set content animation running.
+        assertTrue(view.setContentAnimationRunning(true))
+
+        // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
+        // called on them.
+        verify(mockContracted).setAnimationsRunning(true)
+        verify(mockExpanded).setAnimationsRunning(true)
+        verify(mockHeadsUp).setAnimationsRunning(true)
+
+        // When: we set content animation running false, the state changes, so the function
+        // returns true.
+        assertTrue(view.setContentAnimationRunning(false))
+
+        // Then: the children have their animations stopped.
+        verify(mockContracted).setAnimationsRunning(false)
+        verify(mockExpanded).setAnimationsRunning(false)
+        verify(mockHeadsUp).setAnimationsRunning(false)
+    }
+
+    @Test
+    fun onSetAnimationInitStopped() {
+        // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
+        val mockContracted = mock<NotificationViewWrapper>()
+        val mockExpanded = mock<NotificationViewWrapper>()
+        val mockHeadsUp = mock<NotificationViewWrapper>()
+
+        view.setContractedWrapper(mockContracted)
+        view.setExpandedWrapper(mockExpanded)
+        view.setHeadsUpWrapper(mockHeadsUp)
+
+        // When: we try to stop the animations before they've been started.
+        assertFalse(view.setContentAnimationRunning(false))
+
+        // Then: the children should not have setAnimationRunning called on them again.
+        verify(mockContracted, never()).setAnimationsRunning(false)
+        verify(mockExpanded, never()).setAnimationsRunning(false)
+        verify(mockHeadsUp, never()).setAnimationsRunning(false)
+    }
+
     private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
         mock<ExpandableNotificationRow>().apply {
             whenever(this.entry).thenReturn(notificationEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index b6a1bb3..d7ac6b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -66,9 +66,9 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -133,18 +133,13 @@
     @Mock private ShadeController mShadeController;
     @Mock private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
     @Mock private AssistantFeedbackController mAssistantFeedbackController;
+    @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+    @Mock private StatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() {
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
-        mDependency.injectTestDependency(DeviceProvisionedController.class,
-                mDeviceProvisionedController);
-        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mDependency.injectTestDependency(
-                OnUserInteractionCallback.class,
-                mOnUserInteractionCallback);
-        mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
         mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
@@ -155,7 +150,11 @@
                 mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
                 mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
                 Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
-                mShadeController);
+                mShadeController,
+                mNotificationLockscreenUserManager,
+                mStatusBarStateController,
+                mDeviceProvisionedController,
+                mMetricsLogger);
         mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
                 mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -372,7 +371,8 @@
                 eq(false),
                 eq(false),
                 eq(true), /* wasShownHighPriority */
-                eq(mAssistantFeedbackController));
+                eq(mAssistantFeedbackController),
+                any(MetricsLogger.class));
     }
 
     @Test
@@ -406,7 +406,8 @@
                 eq(true),
                 eq(false),
                 eq(false), /* wasShownHighPriority */
-                eq(mAssistantFeedbackController));
+                eq(mAssistantFeedbackController),
+                any(MetricsLogger.class));
     }
 
     @Test
@@ -438,7 +439,8 @@
                 eq(false),
                 eq(false),
                 eq(false), /* wasShownHighPriority */
-                eq(mAssistantFeedbackController));
+                eq(mAssistantFeedbackController),
+                any(MetricsLogger.class));
     }
 
     ////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 80a81a5..8dd0488 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -130,7 +130,6 @@
         mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
 
         mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
-        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
         // Inflate the layout
         final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
         mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
@@ -194,7 +193,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
         assertTrue(textView.getText().toString().contains("App Name"));
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -220,7 +220,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final ImageView iconView = mNotificationInfo.findViewById(R.id.pkg_icon);
         assertEquals(iconDrawable, iconView.getDrawable());
     }
@@ -242,7 +243,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(GONE, nameView.getVisibility());
     }
@@ -273,7 +275,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
         assertEquals(VISIBLE, nameView.getVisibility());
         assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -296,7 +299,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(GONE, groupNameView.getVisibility());
     }
@@ -324,7 +328,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView groupNameView = mNotificationInfo.findViewById(R.id.group_name);
         assertEquals(View.VISIBLE, groupNameView.getVisibility());
         assertEquals("Test Group Name", groupNameView.getText());
@@ -347,7 +352,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(TEST_CHANNEL_NAME, textView.getText());
     }
@@ -369,7 +375,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(GONE, textView.getVisibility());
     }
@@ -395,7 +402,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(VISIBLE, textView.getVisibility());
     }
@@ -417,7 +425,8 @@
                 true,
                 true,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView textView = mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(VISIBLE, textView.getVisibility());
     }
@@ -443,7 +452,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         settingsButton.performClick();
@@ -468,7 +478,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -493,7 +504,8 @@
                 false,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertTrue(settingsButton.getVisibility() != View.VISIBLE);
     }
@@ -515,7 +527,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         mNotificationInfo.bindNotification(
                 mMockPackageManager,
                 mMockINotificationManager,
@@ -531,7 +544,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final View settingsButton = mNotificationInfo.findViewById(R.id.info);
         assertEquals(View.VISIBLE, settingsButton.getVisibility());
     }
@@ -556,7 +570,8 @@
                 true,
                 true,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.info).performClick();
         // Verify that listener was triggered.
@@ -582,7 +597,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView channelNameView =
                 mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(GONE, channelNameView.getVisibility());
@@ -606,7 +622,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         assertEquals(GONE, mNotificationInfo.findViewById(
                 R.id.interruptiveness_settings).getVisibility());
         assertEquals(VISIBLE, mNotificationInfo.findViewById(
@@ -630,7 +647,8 @@
                 true,
                 true,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text);
         assertEquals(View.VISIBLE, view.getVisibility());
         assertEquals(mContext.getString(R.string.notification_unblockable_desc),
@@ -673,7 +691,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_call_text);
         assertEquals(View.VISIBLE, view.getVisibility());
         assertEquals(mContext.getString(R.string.notification_unblockable_call_desc),
@@ -716,7 +735,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         assertEquals(GONE,
                 mNotificationInfo.findViewById(R.id.non_configurable_call_text).getVisibility());
         assertEquals(VISIBLE,
@@ -743,7 +763,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic).getVisibility());
         assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility());
     }
@@ -765,7 +786,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic).getVisibility());
         assertEquals(GONE, mNotificationInfo.findViewById(R.id.automatic_summary).getVisibility());
     }
@@ -789,7 +811,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         assertTrue(mNotificationInfo.findViewById(R.id.automatic).isSelected());
     }
 
@@ -810,7 +833,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         assertTrue(mNotificationInfo.findViewById(R.id.alert).isSelected());
     }
 
@@ -831,7 +855,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         assertTrue(mNotificationInfo.findViewById(R.id.silence).isSelected());
     }
 
@@ -852,7 +877,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         mTestableLooper.processAllMessages();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), eq(TEST_UID), any());
@@ -875,7 +901,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
         assertEquals(1, mUiEventLogger.numLogs());
         assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(),
                 mUiEventLogger.eventId(0));
@@ -899,7 +926,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
         mTestableLooper.processAllMessages();
@@ -926,7 +954,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.silence).performClick();
         mTestableLooper.processAllMessages();
@@ -953,7 +982,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.automatic).performClick();
         mTestableLooper.processAllMessages();
@@ -981,7 +1011,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.handleCloseControls(true, false);
         mTestableLooper.processAllMessages();
@@ -1008,7 +1039,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.handleCloseControls(true, false);
         mTestableLooper.processAllMessages();
@@ -1043,7 +1075,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.handleCloseControls(true, false);
 
@@ -1071,7 +1104,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.silence).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1112,7 +1146,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1149,7 +1184,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.automatic).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1181,7 +1217,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.silence).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1217,7 +1254,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         assertEquals(mContext.getString(R.string.inline_done_button),
                 ((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -1255,7 +1293,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.silence).performClick();
         mNotificationInfo.handleCloseControls(false, false);
@@ -1286,7 +1325,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         assertEquals(mContext.getString(R.string.inline_done_button),
                 ((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -1324,7 +1364,8 @@
                 true,
                 false,
                 true,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.silence).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1353,7 +1394,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         assertEquals(mContext.getString(R.string.inline_done_button),
                 ((TextView) mNotificationInfo.findViewById(R.id.done)).getText());
@@ -1384,7 +1426,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1419,7 +1462,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1452,7 +1496,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
         mNotificationInfo.findViewById(R.id.done).performClick();
@@ -1485,7 +1530,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         mNotificationInfo.findViewById(R.id.alert).performClick();
 
@@ -1511,7 +1557,8 @@
                 true,
                 false,
                 false,
-                mAssistantFeedbackController);
+                mAssistantFeedbackController,
+                mMetricsLogger);
 
         assertFalse(mNotificationInfo.willBeRemoved());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 728e026..aca9c56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -40,7 +40,6 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.graphics.drawable.Icon;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.testing.TestableLooper;
@@ -49,14 +48,13 @@
 import android.widget.RemoteViews;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -67,7 +65,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.icon.IconBuilder;
@@ -76,11 +73,8 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -120,16 +114,17 @@
     private final GroupMembershipManager mGroupMembershipManager;
     private final GroupExpansionManager mGroupExpansionManager;
     private ExpandableNotificationRow mRow;
-    private HeadsUpManagerPhone mHeadsUpManager;
+    private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotifBindPipeline mBindPipeline;
     private final NotifCollectionListener mBindPipelineEntryListener;
     private final RowContentBindStage mBindStage;
     private final IconManager mIconManager;
-    private StatusBarStateController mStatusBarStateController;
+    private final StatusBarStateController mStatusBarStateController;
     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
     public final OnUserInteractionCallback mOnUserInteractionCallback;
     public final Runnable mFutureDismissalRunnable;
     private @InflationFlag int mDefaultInflationFlags;
+    private FeatureFlags mFeatureFlags;
 
     public NotificationTestHelper(
             Context context,
@@ -144,21 +139,7 @@
         mStatusBarStateController = mock(StatusBarStateController.class);
         mGroupMembershipManager = mock(GroupMembershipManager.class);
         mGroupExpansionManager = mock(GroupExpansionManager.class);
-        mHeadsUpManager = new HeadsUpManagerPhone(
-                mContext,
-                mock(HeadsUpManagerLogger.class),
-                mStatusBarStateController,
-                mock(KeyguardBypassController.class),
-                mock(GroupMembershipManager.class),
-                mock(VisualStabilityProvider.class),
-                mock(ConfigurationControllerImpl.class),
-                new Handler(mTestLooper.getLooper()),
-                mock(AccessibilityManagerWrapper.class),
-                mock(UiEventLogger.class),
-                mock(ShadeExpansionStateManager.class)
-        );
-        mHeadsUpManager.mHandler.removeCallbacksAndMessages(null);
-        mHeadsUpManager.mHandler = new Handler(mTestLooper.getLooper());
+        mHeadsUpManager = mock(HeadsUpManagerPhone.class);
         mIconManager = new IconManager(
                 mock(CommonNotifCollection.class),
                 mock(LauncherApps.class),
@@ -193,12 +174,17 @@
         mFutureDismissalRunnable = mock(Runnable.class);
         when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
                 .thenReturn(mFutureDismissalRunnable);
+        mFeatureFlags = mock(FeatureFlags.class);
     }
 
     public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
         mDefaultInflationFlags = defaultInflationFlags;
     }
 
+    public void setFeatureFlags(FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
+    }
+
     public ExpandableNotificationRowLogger getMockLogger() {
         return mMockLogger;
     }
@@ -561,7 +547,8 @@
                 mock(NotificationGutsManager.class),
                 mock(MetricsLogger.class),
                 mock(SmartReplyConstants.class),
-                mock(SmartReplyController.class));
+                mock(SmartReplyController.class),
+                mFeatureFlags);
 
         row.setAboveShelfChangedListener(aboveShelf -> { });
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
index 509ba41..8f88501 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
@@ -16,11 +16,12 @@
 
 package com.android.systemui.statusbar.notification.row.wrapper;
 
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.app.Notification;
-import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.testing.AndroidTestingRunner;
@@ -28,11 +29,11 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
+import com.android.internal.widget.BigPictureNotificationImageView;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -73,4 +74,38 @@
                 Notification.EXTRA_LARGE_ICON_BIG, new Bundle());
         wrapper.onContentUpdated(mRow);
     }
+
+    @Test
+    public void setAnimationsRunning_Run() {
+        BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
+        AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
+
+        assertNotNull(imageView);
+        imageView.setImageDrawable(mockDrawable);
+
+        NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
+                mView, mRow);
+        // Required to re-initialize the imageView to the imageView created above.
+        wrapper.onContentUpdated(mRow);
+
+        wrapper.setAnimationsRunning(true);
+        verify(mockDrawable).start();
+    }
+
+    @Test
+    public void setAnimationsRunning_Stop() {
+        BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
+        AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
+
+        assertNotNull(imageView);
+        imageView.setImageDrawable(mockDrawable);
+
+        NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
+                mView, mRow);
+        // Required to re-initialize the imageView to the imageView created above.
+        wrapper.onContentUpdated(mRow);
+
+        wrapper.setAnimationsRunning(false);
+        verify(mockDrawable).stop();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
new file mode 100644
index 0000000..3fa68bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.systemui.statusbar.notification.row.wrapper
+
+import android.graphics.drawable.AnimatedImageDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.widget.CachingIconView
+import com.android.internal.widget.ConversationLayout
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
+import com.android.internal.widget.MessagingLinearLayout
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() {
+
+    private lateinit var mRow: ExpandableNotificationRow
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        mRow = helper.createRow()
+    }
+
+    @Test
+    fun setAnimationsRunning_Run() {
+        // Creates a mocked out NotificationEntry of ConversationLayout type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessages
+        // (both top level, and in a group).
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockDrawable2 = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.onContentUpdated(mRow)
+        wrapper.setAnimationsRunning(true)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).start()
+        verify(mockDrawable2).start()
+    }
+
+    @Test
+    fun setAnimationsRunning_Stop() {
+        // Creates a mocked out NotificationEntry of ConversationLayout type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessages
+        // (both top level, and in a group).
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockDrawable2 = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.onContentUpdated(mRow)
+        wrapper.setAnimationsRunning(false)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).stop()
+        verify(mockDrawable2).stop()
+    }
+
+    private fun fakeConversationLayout(
+        mockDrawableGroupMessage: AnimatedImageDrawable,
+        mockDrawableImageMessage: AnimatedImageDrawable
+    ): View {
+        val mockMessagingImageMessage: MessagingImageMessage =
+            mock<MessagingImageMessage>().apply {
+                whenever(drawable).thenReturn(mockDrawableImageMessage)
+            }
+        val mockImageMessageContainer: MessagingLinearLayout =
+            mock<MessagingLinearLayout>().apply {
+                whenever(childCount).thenReturn(1)
+                whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
+            }
+
+        val mockMessagingImageMessageForGroup: MessagingImageMessage =
+            mock<MessagingImageMessage>().apply {
+                whenever(drawable).thenReturn(mockDrawableGroupMessage)
+            }
+        val mockMessageContainer: MessagingLinearLayout =
+            mock<MessagingLinearLayout>().apply {
+                whenever(childCount).thenReturn(1)
+                whenever(getChildAt(any())).thenReturn(mockMessagingImageMessageForGroup)
+            }
+        val mockGroup: MessagingGroup =
+            mock<MessagingGroup>().apply {
+                whenever(messageContainer).thenReturn(mockMessageContainer)
+            }
+        val mockView: View =
+            mock<ConversationLayout>().apply {
+                whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
+                whenever(imageMessageContainer).thenReturn(mockImageMessageContainer)
+                whenever(messagingLinearLayout).thenReturn(mockMessageContainer)
+
+                // These must be mocked as they're required to be nonnull.
+                whenever(requireViewById<View>(R.id.conversation_icon_container)).thenReturn(mock())
+                whenever(requireViewById<CachingIconView>(R.id.conversation_icon))
+                    .thenReturn(mock())
+                whenever(findViewById<CachingIconView>(R.id.icon)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.conversation_icon_badge_bg)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.expand_button)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.expand_button_container)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.conversation_icon_badge_ring))
+                    .thenReturn(mock())
+                whenever(requireViewById<View>(R.id.app_name_text)).thenReturn(mock())
+                whenever(requireViewById<View>(R.id.conversation_text)).thenReturn(mock())
+            }
+        return mockView
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
new file mode 100644
index 0000000..c0444b5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 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.systemui.statusbar.notification.row.wrapper
+
+import android.graphics.drawable.AnimatedImageDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
+import com.android.internal.widget.MessagingLayout
+import com.android.internal.widget.MessagingLinearLayout
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() {
+
+    private lateinit var mRow: ExpandableNotificationRow
+    private lateinit var helper: NotificationTestHelper
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+        mRow = helper.createRow()
+    }
+
+    @Test
+    fun setAnimationsRunning_Run() {
+        // Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessage.
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.setAnimationsRunning(true)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).start()
+    }
+
+    @Test
+    fun setAnimationsRunning_Stop() {
+        // Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
+        // with a mock imageMessage.drawable embedded in its MessagingImageMessage.
+        val mockDrawable = mock<AnimatedImageDrawable>()
+        val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
+
+        val wrapper: NotificationViewWrapper =
+            NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
+        wrapper.setAnimationsRunning(false)
+
+        // Verifies that each AnimatedImageDrawable is started animating.
+        verify(mockDrawable).stop()
+    }
+
+    private fun fakeMessagingLayout(mockDrawable: AnimatedImageDrawable): View {
+        val mockMessagingImageMessage: MessagingImageMessage =
+            mock<MessagingImageMessage>().apply { whenever(drawable).thenReturn(mockDrawable) }
+        val mockMessageContainer: MessagingLinearLayout =
+            mock<MessagingLinearLayout>().apply {
+                whenever(childCount).thenReturn(1)
+                whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
+            }
+        val mockGroup: MessagingGroup =
+            mock<MessagingGroup>().apply {
+                whenever(messageContainer).thenReturn(mockMessageContainer)
+            }
+        val mockView: View =
+            mock<MessagingLayout>().apply {
+                whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
+            }
+        return mockView
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 645052f..9f6f082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -65,6 +66,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl;
 import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifStats;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -78,6 +80,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -135,10 +138,14 @@
     @Mock private ShadeTransitionController mShadeTransitionController;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private NotificationTargetsHelper mNotificationTargetsHelper;
+    @Mock private SecureSettings mSecureSettings;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
 
+    private final SeenNotificationsProviderImpl mSeenNotificationsProvider =
+            new SeenNotificationsProviderImpl();
+
     private NotificationStackScrollLayoutController mController;
 
     @Before
@@ -180,14 +187,15 @@
                 mUiEventLogger,
                 mRemoteInputManager,
                 mVisibilityLocationProviderDelegator,
-                new SeenNotificationsProviderImpl(),
+                mSeenNotificationsProvider,
                 mShadeController,
                 mJankMonitor,
                 mStackLogger,
                 mLogger,
                 mNotificationStackSizeCalculator,
                 mFeatureFlags,
-                mNotificationTargetsHelper
+                mNotificationTargetsHelper,
+                mSecureSettings
         );
 
         when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(true);
@@ -233,16 +241,14 @@
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ true,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ true);
 
         setupShowEmptyShadeViewState(false);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ false,
-                /* notifVisibleInShade= */ true,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ true);
     }
 
     @Test
@@ -255,16 +261,14 @@
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ false,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ false);
 
         setupShowEmptyShadeViewState(false);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ false,
-                /* notifVisibleInShade= */ false,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ false);
     }
 
     @Test
@@ -283,16 +287,14 @@
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ false,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ false);
 
         mController.setQsFullScreen(true);
         reset(mNotificationStackScrollLayout);
         mController.updateShowEmptyShadeView();
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(
                 /* visible= */ true,
-                /* notifVisibleInShade= */ false,
-                /* areSeenNotifsFiltered= */false);
+                /* notifVisibleInShade= */ false);
     }
 
     @Test
@@ -400,6 +402,17 @@
         verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true);
     }
 
+    @Test
+    public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
+        when(mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()).thenReturn(true);
+        mSeenNotificationsProvider.setHasFilteredOutSeenNotifications(true);
+        mController.attach(mNotificationStackScrollLayout);
+        mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
+        verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
+        verify(mNotificationStackScrollLayout).updateFooter();
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(anyBoolean(), anyBoolean());
+    }
+
     private LogMaker logMatcher(int category, int type) {
         return argThat(new LogMatcher(category, type));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 7622549..dd7143a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -30,6 +30,7 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertFalse;
+import static org.mockito.AdditionalMatchers.not;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -53,6 +54,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.TextView;
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
@@ -328,7 +330,7 @@
     public void updateEmptyView_dndSuppressing() {
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, true, false);
+        mStackScroller.updateEmptyShadeView(true, true);
 
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
@@ -338,7 +340,7 @@
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
 
-        mStackScroller.updateEmptyShadeView(true, false, false);
+        mStackScroller.updateEmptyShadeView(true, false);
 
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
     }
@@ -347,10 +349,10 @@
     public void updateEmptyView_noNotificationsToDndSuppressing() {
         mStackScroller.setEmptyShadeView(mEmptyShadeView);
         when(mEmptyShadeView.willBeGone()).thenReturn(true);
-        mStackScroller.updateEmptyShadeView(true, false, false);
+        mStackScroller.updateEmptyShadeView(true, false);
         verify(mEmptyShadeView).setText(R.string.empty_shade_text);
 
-        mStackScroller.updateEmptyShadeView(true, true, false);
+        mStackScroller.updateEmptyShadeView(true, true);
         verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
     }
 
@@ -818,6 +820,29 @@
         assertEquals(0f, mAmbientState.getStackY());
     }
 
+    @Test
+    public void hasFilteredOutSeenNotifs_updateFooter() {
+        mStackScroller.setCurrentUserSetup(true);
+
+        // add footer
+        mStackScroller.inflateFooterView();
+        TextView footerLabel =
+                mStackScroller.mFooterView.requireViewById(R.id.unlock_prompt_footer);
+
+        mStackScroller.setHasFilteredOutSeenNotifications(true);
+        mStackScroller.updateFooter();
+
+        assertThat(footerLabel.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
+        mStackScroller.setHasFilteredOutSeenNotifications(true);
+        mStackScroller.updateEmptyShadeView(true, false);
+
+        verify(mEmptyShadeView).setFooterText(not(0));
+    }
+
     private void setBarStateForTest(int state) {
         // Can't inject this through the listener or we end up on the actual implementation
         // rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 091bb54..8cfcc07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -510,6 +510,7 @@
     @Test
     public void managedProfileAdded_tileAdded() {
         when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false);
+        when(mAutoAddTracker.getRestoredTilePosition(eq("work"))).thenReturn(2);
         mAutoTileManager = createAutoTileManager(mContext);
         Mockito.doAnswer((Answer<Object>) invocation -> {
             mManagedProfileCallback = invocation.getArgument(0);
@@ -520,7 +521,7 @@
 
         mManagedProfileCallback.onManagedProfileChanged();
 
-        verify(mQsTileHost, times(1)).addTile(eq("work"));
+        verify(mQsTileHost, times(1)).addTile(eq("work"), eq(2));
         verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 3fccd37..b879cf2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -25,8 +25,10 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.StatusBarManager;
 import android.os.PowerManager;
+import android.os.UserHandle;
 import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
 import android.view.InsetsVisibilities;
@@ -41,6 +43,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.ShadeController;
@@ -88,6 +91,7 @@
     @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock private SystemBarAttributesListener mSystemBarAttributesListener;
     @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
+    @Mock private UserTracker mUserTracker;
 
     CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
 
@@ -120,8 +124,11 @@
                 new DisableFlagsLogger(),
                 DEFAULT_DISPLAY,
                 mSystemBarAttributesListener,
-                mCameraLauncherLazy);
+                mCameraLauncherLazy,
+                mUserTracker);
 
+        when(mUserTracker.getUserHandle()).thenReturn(
+                UserHandle.of(ActivityManager.getCurrentUser()));
         when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
         when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt()))
                 .thenAnswer((Answer<Integer>) invocation -> invocation.getArgument(0));
@@ -140,7 +147,7 @@
 
         // Trying to open it does nothing.
         mSbcqCallbacks.animateExpandNotificationsPanel();
-        verify(mNotificationPanelViewController, never()).expandWithoutQs();
+        verify(mNotificationPanelViewController, never()).expandShadeToNotifications();
         mSbcqCallbacks.animateExpandSettingsPanel(null);
         verify(mNotificationPanelViewController, never()).expand(anyBoolean());
     }
@@ -158,7 +165,7 @@
 
         // Can now be opened.
         mSbcqCallbacks.animateExpandNotificationsPanel();
-        verify(mNotificationPanelViewController).expandWithoutQs();
+        verify(mNotificationPanelViewController).expandShadeToNotifications();
         mSbcqCallbacks.animateExpandSettingsPanel(null);
         verify(mNotificationPanelViewController).expandWithQs();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c8157cc..0605398 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -44,6 +44,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.IWallpaperManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -118,6 +119,7 @@
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.NotificationPanelView;
@@ -306,6 +308,7 @@
      */
     @Mock private ViewRootImpl mViewRootImpl;
     @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+    @Mock private UserTracker mUserTracker;
     @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
     @Mock IPowerManager mPowerManagerService;
 
@@ -321,6 +324,10 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        // CentralSurfacesImpl's runtime flag check fails if the flag is absent.
+        // This value is unused, because test manifest is opted in.
+        mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
+
         IThermalService thermalService = mock(IThermalService.class);
         mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
                 Handler.createAsync(Looper.myLooper()));
@@ -338,7 +345,8 @@
                         new Handler(TestableLooper.get(this).getLooper()),
                         mock(NotifPipelineFlags.class),
                         mock(KeyguardNotificationVisibilityProvider.class),
-                        mock(UiEventLogger.class));
+                        mock(UiEventLogger.class),
+                        mUserTracker);
 
         mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
         mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -419,6 +427,9 @@
 
         when(mOperatorNameViewControllerFactory.create(any()))
                 .thenReturn(mOperatorNameViewController);
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+        when(mUserTracker.getUserHandle()).thenReturn(
+                UserHandle.of(ActivityManager.getCurrentUser()));
 
         mCentralSurfaces = new CentralSurfacesImpl(
                 mContext,
@@ -508,7 +519,8 @@
                 mDreamManager,
                 mCameraLauncherLazy,
                 () -> mLightRevealScrimViewModel,
-                mAlternateBouncerInteractor
+                mAlternateBouncerInteractor,
+                mUserTracker
         ) {
             @Override
             protected ViewRootImpl getViewRootImpl() {
@@ -544,6 +556,7 @@
         mCentralSurfaces.startKeyguard();
         mInitController.executePostInitTasks();
         notificationLogger.setUpWithContainer(mNotificationListContainer);
+        mCentralSurfaces.registerCallbacks();
     }
 
     @Test
@@ -997,6 +1010,18 @@
     }
 
     @Test
+    public void testSetDozingNotUnlocking_transitionToAOD_cancelKeyguardFadingAway() {
+        setDozing(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+        when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
+
+        mCentralSurfaces.updateScrimController();
+
+        verify(mScrimController, times(2)).transitionTo(eq(ScrimState.AOD));
+        verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway();
+    }
+
+    @Test
     public void testShowKeyguardImplementation_setsState() {
         when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>());
 
@@ -1279,7 +1304,8 @@
                 Handler mainHandler,
                 NotifPipelineFlags flags,
                 KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
-                UiEventLogger uiEventLogger) {
+                UiEventLogger uiEventLogger,
+                UserTracker userTracker) {
             super(
                     contentResolver,
                     powerManager,
@@ -1293,7 +1319,8 @@
                     mainHandler,
                     flags,
                     keyguardNotificationVisibilityProvider,
-                    uiEventLogger
+                    uiEventLogger,
+                    userTracker
             );
             mUseHeadsUp = true;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index c843850..eb5edbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.content.res.Resources;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Handler;
@@ -44,6 +45,7 @@
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -82,6 +84,7 @@
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private ConfigurationController mConfigurationController;
+    @Mock private UserTracker mUserTracker;
     @Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback;
 
     /**
@@ -107,6 +110,7 @@
 
         when(mSysUIUnfoldComponent.getFoldAodAnimationController())
                 .thenReturn(mFoldAodAnimationController);
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
 
         mDozeParameters = new DozeParameters(
             mContext,
@@ -123,7 +127,8 @@
             mUnlockedScreenOffAnimationController,
             mKeyguardUpdateMonitor,
             mConfigurationController,
-            mStatusBarStateController
+            mStatusBarStateController,
+            mUserTracker
         );
 
         verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
deleted file mode 100644
index 7ad9cc2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2017 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.systemui.statusbar.phone;
-
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.Handler;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardHostViewController;
-import com.android.keyguard.KeyguardSecurityModel;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.ViewMediatorCallback;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class KeyguardBouncerTest extends SysuiTestCase {
-
-    @Mock
-    private FalsingCollector mFalsingCollector;
-    @Mock
-    private ViewMediatorCallback mViewMediatorCallback;
-    @Mock
-    private DismissCallbackRegistry mDismissCallbackRegistry;
-    @Mock
-    private KeyguardHostViewController mKeyguardHostViewController;
-    @Mock
-    private KeyguardBouncer.PrimaryBouncerExpansionCallback mExpansionCallback;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardBypassController mKeyguardBypassController;
-    @Mock
-    private Handler mHandler;
-    @Mock
-    private KeyguardSecurityModel mKeyguardSecurityModel;
-    @Mock
-    private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
-    @Mock
-    private KeyguardBouncerComponent mKeyguardBouncerComponent;
-    private ViewGroup mContainer;
-    @Rule
-    public MockitoRule mRule = MockitoJUnit.rule();
-    private Integer mRootVisibility = View.INVISIBLE;
-    private KeyguardBouncer mBouncer;
-
-    @Before
-    public void setup() {
-        allowTestableLooperAsMainThread();
-        when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
-                .thenReturn(KeyguardSecurityModel.SecurityMode.None);
-        DejankUtils.setImmediate(true);
-
-        mContainer = spy(new FrameLayout(getContext()));
-        when(mKeyguardBouncerComponentFactory.create(mContainer)).thenReturn(
-                mKeyguardBouncerComponent);
-        when(mKeyguardBouncerComponent.getKeyguardHostViewController())
-                .thenReturn(mKeyguardHostViewController);
-
-        mBouncer = new KeyguardBouncer.Factory(getContext(), mViewMediatorCallback,
-                mDismissCallbackRegistry, mFalsingCollector,
-                mKeyguardStateController, mKeyguardUpdateMonitor,
-                mKeyguardBypassController, mHandler, mKeyguardSecurityModel,
-                mKeyguardBouncerComponentFactory)
-                .create(mContainer, mExpansionCallback);
-    }
-
-    @Test
-    public void testInflateView_doesntCrash() {
-        mBouncer.inflateView();
-    }
-
-    @Test
-    public void testShow_notifiesFalsingManager() {
-        mBouncer.show(true);
-        verify(mFalsingCollector).onBouncerShown();
-
-        mBouncer.show(true, false);
-        verifyNoMoreInteractions(mFalsingCollector);
-    }
-
-    /**
-     * Regression test: Invisible bouncer when occluded.
-     */
-    @Test
-    public void testShow_bouncerIsVisible() {
-        // Expand notification panel as if we were in the keyguard.
-        mBouncer.ensureView();
-        mBouncer.setExpansion(1);
-
-        reset(mKeyguardHostViewController);
-
-        mBouncer.show(true);
-        verify(mKeyguardHostViewController).setExpansion(0);
-    }
-
-    @Test
-    public void testShow_notifiesVisibility() {
-        mBouncer.show(true);
-        verify(mKeyguardStateController).notifyBouncerShowing(eq(true));
-        verify(mExpansionCallback).onStartingToShow();
-
-        // Not called again when visible
-        reset(mViewMediatorCallback);
-        mBouncer.show(true);
-        verifyNoMoreInteractions(mViewMediatorCallback);
-    }
-
-    @Test
-    public void testShow_triesToDismissKeyguard() {
-        mBouncer.show(true);
-        verify(mKeyguardHostViewController).dismiss(anyInt());
-    }
-
-    @Test
-    public void testShow_resetsSecuritySelection() {
-        mBouncer.show(false);
-        verify(mKeyguardHostViewController, never()).showPrimarySecurityScreen();
-
-        mBouncer.hide(false);
-        mBouncer.show(true);
-        verify(mKeyguardHostViewController).showPrimarySecurityScreen();
-    }
-
-    @Test
-    public void testShow_animatesKeyguardView() {
-        mBouncer.show(true);
-        verify(mKeyguardHostViewController).appear(anyInt());
-    }
-
-    @Test
-    public void testShow_showsErrorMessage() {
-        final String errorMessage = "an error message";
-        when(mViewMediatorCallback.consumeCustomMessage()).thenReturn(errorMessage);
-        mBouncer.show(true);
-        verify(mKeyguardHostViewController).showErrorMessage(eq(errorMessage));
-    }
-
-    @Test
-    public void testSetExpansion_notifiesFalsingManager() {
-        mBouncer.ensureView();
-        mBouncer.setExpansion(0.5f);
-
-        mBouncer.setExpansion(EXPANSION_HIDDEN);
-        verify(mFalsingCollector).onBouncerHidden();
-        verify(mExpansionCallback).onFullyHidden();
-
-        mBouncer.setExpansion(EXPANSION_VISIBLE);
-        verify(mFalsingCollector).onBouncerShown();
-        verify(mExpansionCallback).onFullyShown();
-
-        verify(mExpansionCallback, never()).onStartingToHide();
-        verify(mKeyguardHostViewController, never()).onStartingToHide();
-        mBouncer.setExpansion(0.9f);
-        verify(mExpansionCallback).onStartingToHide();
-        verify(mKeyguardHostViewController).onStartingToHide();
-    }
-
-    @Test
-    public void testSetExpansion_notifiesKeyguardView() {
-        mBouncer.ensureView();
-        mBouncer.setExpansion(0.1f);
-
-        mBouncer.setExpansion(0);
-        verify(mKeyguardHostViewController).onResume();
-        verify(mContainer).announceForAccessibility(any());
-    }
-
-    @Test
-    public void show_notifiesKeyguardViewController() {
-        mBouncer.ensureView();
-
-        mBouncer.show(/* resetSecuritySelection= */ false);
-
-        verify(mKeyguardHostViewController).onBouncerVisibilityChanged(View.VISIBLE);
-    }
-
-    @Test
-    public void testHide_notifiesFalsingManager() {
-        mBouncer.hide(false);
-        verify(mFalsingCollector).onBouncerHidden();
-    }
-
-    @Test
-    public void testHide_notifiesVisibility() {
-        mBouncer.hide(false);
-        verify(mKeyguardStateController).notifyBouncerShowing(eq(false));
-    }
-
-    @Test
-    public void testHide_notifiesDismissCallbackIfVisible() {
-        mBouncer.hide(false);
-        verifyZeroInteractions(mDismissCallbackRegistry);
-        mBouncer.show(false);
-        mBouncer.hide(false);
-        verify(mDismissCallbackRegistry).notifyDismissCancelled();
-    }
-
-    @Test
-    public void testHide_notShowingAnymore() {
-        mBouncer.ensureView();
-        mBouncer.show(false /* resetSecuritySelection */);
-        mBouncer.hide(false /* destroyViews */);
-        Assert.assertFalse("Not showing", mBouncer.isShowing());
-    }
-
-    @Test
-    public void testShowPromptReason_propagates() {
-        mBouncer.ensureView();
-        mBouncer.showPromptReason(1);
-        verify(mKeyguardHostViewController).showPromptReason(eq(1));
-    }
-
-    @Test
-    public void testShowMessage_propagates() {
-        final String message = "a message";
-        mBouncer.ensureView();
-        mBouncer.showMessage(message, ColorStateList.valueOf(Color.GREEN));
-        verify(mKeyguardHostViewController).showMessage(
-                eq(message), eq(ColorStateList.valueOf(Color.GREEN)));
-    }
-
-    @Test
-    public void testShowOnDismissAction_showsBouncer() {
-        final OnDismissAction dismissAction = () -> false;
-        final Runnable cancelAction = () -> {};
-        mBouncer.showWithDismissAction(dismissAction, cancelAction);
-        verify(mKeyguardHostViewController).setOnDismissAction(dismissAction, cancelAction);
-        Assert.assertTrue("Should be showing", mBouncer.isShowing());
-    }
-
-    @Test
-    public void testStartPreHideAnimation_notifiesView() {
-        final boolean[] ran = {false};
-        final Runnable r = () -> ran[0] = true;
-        mBouncer.startPreHideAnimation(r);
-        Assert.assertTrue("Callback should have been invoked", ran[0]);
-
-        ran[0] = false;
-        mBouncer.ensureView();
-        mBouncer.startPreHideAnimation(r);
-        verify(mKeyguardHostViewController).startDisappearAnimation(r);
-        Assert.assertFalse("Callback should have been deferred", ran[0]);
-    }
-
-    @Test
-    public void testIsShowing_animated() {
-        Assert.assertFalse("Show wasn't invoked yet", mBouncer.isShowing());
-        mBouncer.show(true /* reset */);
-        Assert.assertTrue("Should be showing", mBouncer.isShowing());
-    }
-
-    @Test
-    public void testIsShowing_forSwipeUp() {
-        mBouncer.setExpansion(1f);
-        mBouncer.show(true /* reset */, false /* animated */);
-        Assert.assertFalse("Should only be showing after collapsing notification panel",
-                mBouncer.isShowing());
-        mBouncer.setExpansion(0f);
-        Assert.assertTrue("Should be showing", mBouncer.isShowing());
-    }
-
-    @Test
-    public void testSetExpansion() {
-        mBouncer.ensureView();
-        mBouncer.setExpansion(0.5f);
-        verify(mKeyguardHostViewController).setExpansion(0.5f);
-    }
-
-    @Test
-    public void testIsFullscreenBouncer_asksKeyguardView() {
-        mBouncer.ensureView();
-        mBouncer.isFullscreenBouncer();
-        verify(mKeyguardHostViewController).getCurrentSecurityMode();
-    }
-
-    @Test
-    public void testIsHiding_preHideOrHide() {
-        Assert.assertFalse("Should not be hiding on initial state", mBouncer.isAnimatingAway());
-        mBouncer.startPreHideAnimation(null /* runnable */);
-        Assert.assertTrue("Should be hiding during pre-hide", mBouncer.isAnimatingAway());
-        mBouncer.hide(false /* destroyView */);
-        Assert.assertFalse("Should be hidden after hide()", mBouncer.isAnimatingAway());
-    }
-
-    @Test
-    public void testIsHiding_skipsTranslation() {
-        mBouncer.show(false /* reset */);
-        reset(mKeyguardHostViewController);
-        mBouncer.startPreHideAnimation(null /* runnable */);
-        mBouncer.setExpansion(0.5f);
-        verify(mKeyguardHostViewController, never()).setExpansion(anyFloat());
-    }
-
-    @Test
-    public void testIsSecure() {
-        mBouncer.ensureView();
-        for (KeyguardSecurityModel.SecurityMode mode : KeyguardSecurityModel.SecurityMode.values()){
-            reset(mKeyguardSecurityModel);
-            when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(mode);
-            Assert.assertEquals("Security doesn't match for mode: " + mode,
-                    mBouncer.isSecure(), mode != KeyguardSecurityModel.SecurityMode.None);
-        }
-    }
-
-    @Test
-    public void testIsShowingScrimmed_true() {
-        doAnswer(invocation -> {
-            assertThat(mBouncer.isScrimmed()).isTrue();
-            return null;
-        }).when(mExpansionCallback).onFullyShown();
-        mBouncer.show(false /* resetSecuritySelection */, true /* animate */);
-        assertThat(mBouncer.isScrimmed()).isTrue();
-        mBouncer.hide(false /* destroyView */);
-        assertThat(mBouncer.isScrimmed()).isFalse();
-    }
-
-    @Test
-    public void testIsShowingScrimmed_false() {
-        doAnswer(invocation -> {
-            assertThat(mBouncer.isScrimmed()).isFalse();
-            return null;
-        }).when(mExpansionCallback).onFullyShown();
-        mBouncer.show(false /* resetSecuritySelection */, false /* animate */);
-        assertThat(mBouncer.isScrimmed()).isFalse();
-    }
-
-    @Test
-    public void testWillDismissWithAction() {
-        mBouncer.ensureView();
-        Assert.assertFalse("Action not set yet", mBouncer.willDismissWithAction());
-        when(mKeyguardHostViewController.hasDismissActions()).thenReturn(true);
-        Assert.assertTrue("Action should exist", mBouncer.willDismissWithAction());
-    }
-
-    @Test
-    public void testShow_delaysIfFaceAuthIsRunning() {
-        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
-                .thenReturn(true);
-        when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
-        mBouncer.show(true /* reset */);
-
-        ArgumentCaptor<Runnable> showRunnable = ArgumentCaptor.forClass(Runnable.class);
-        verify(mHandler).postDelayed(showRunnable.capture(),
-                eq(KeyguardBouncer.BOUNCER_FACE_DELAY));
-
-        mBouncer.hide(false /* destroyView */);
-        verify(mHandler).removeCallbacks(eq(showRunnable.getValue()));
-    }
-
-    @Test
-    public void testShow_doesNotDelaysIfFaceAuthIsNotAllowed() {
-        when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
-                .thenReturn(false);
-        mBouncer.show(true /* reset */);
-
-        verify(mHandler, never()).postDelayed(any(), anyLong());
-    }
-
-    @Test
-    public void testShow_delaysIfFaceAuthIsRunning_unlessBypassEnabled() {
-        when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
-        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
-        mBouncer.show(true /* reset */);
-
-        verify(mHandler, never()).postDelayed(any(), anyLong());
-    }
-
-    @Test
-    public void testShow_delaysIfFaceAuthIsRunning_unlessFingerprintEnrolled() {
-        when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0))
-                .thenReturn(true);
-        mBouncer.show(true /* reset */);
-
-        verify(mHandler, never()).postDelayed(any(), anyLong());
-    }
-
-    @Test
-    public void testRegisterUpdateMonitorCallback() {
-        verify(mKeyguardUpdateMonitor).registerCallback(any());
-    }
-
-    @Test
-    public void testInTransit_whenTranslation() {
-        mBouncer.show(true);
-        mBouncer.setExpansion(EXPANSION_HIDDEN);
-        assertThat(mBouncer.inTransit()).isFalse();
-        mBouncer.setExpansion(0.5f);
-        assertThat(mBouncer.inTransit()).isTrue();
-        mBouncer.setExpansion(EXPANSION_VISIBLE);
-        assertThat(mBouncer.inTransit()).isFalse();
-    }
-
-    @Test
-    public void testUpdateResources_delegatesToRootView() {
-        mBouncer.ensureView();
-        mBouncer.updateResources();
-
-        // This is mocked, so won't pick up on the call to updateResources via
-        // mKeyguardViewController.init(), only updateResources above.
-        verify(mKeyguardHostViewController).updateResources();
-    }
-
-    @Test
-    public void testUpdateKeyguardPosition_delegatesToRootView() {
-        mBouncer.ensureView();
-        mBouncer.updateKeyguardPosition(1.0f);
-
-        verify(mKeyguardHostViewController).updateKeyguardPosition(1.0f);
-    }
-
-    @Test
-    public void testExpansion_notifiesCallback() {
-        mBouncer.ensureView();
-        mBouncer.setExpansion(0.5f);
-
-        final PrimaryBouncerExpansionCallback callback =
-                mock(PrimaryBouncerExpansionCallback.class);
-        mBouncer.addBouncerExpansionCallback(callback);
-
-        mBouncer.setExpansion(EXPANSION_HIDDEN);
-        verify(callback).onFullyHidden();
-        verify(callback).onExpansionChanged(EXPANSION_HIDDEN);
-
-        Mockito.clearInvocations(callback);
-        mBouncer.setExpansion(EXPANSION_VISIBLE);
-        verify(callback).onFullyShown();
-        verify(callback).onExpansionChanged(EXPANSION_VISIBLE);
-
-        Mockito.clearInvocations(callback);
-        float bouncerHideAmount = 0.9f;
-        // Ensure the callback only triggers once despite multiple calls to setExpansion
-        // with the same value.
-        mBouncer.setExpansion(bouncerHideAmount);
-        mBouncer.setExpansion(bouncerHideAmount);
-        verify(callback, times(1)).onStartingToHide();
-        verify(callback, times(1)).onExpansionChanged(bouncerHideAmount);
-
-        Mockito.clearInvocations(callback);
-        mBouncer.removeBouncerExpansionCallback(callback);
-        bouncerHideAmount = 0.5f;
-        mBouncer.setExpansion(bouncerHideAmount);
-        verify(callback, never()).onExpansionChanged(bouncerHideAmount);
-    }
-
-    @Test
-    public void testOnResumeCalledForFullscreenBouncerOnSecondShow() {
-        // GIVEN a security mode which requires fullscreen bouncer
-        when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
-                .thenReturn(KeyguardSecurityModel.SecurityMode.SimPin);
-        mBouncer.show(true);
-
-        // WHEN a second call to show occurs, the bouncer will already by visible
-        reset(mKeyguardHostViewController);
-        mBouncer.show(true);
-
-        // THEN ensure the ViewController is told to resume
-        verify(mKeyguardHostViewController).onResume();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index ed3f710..7e69efa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -264,6 +264,19 @@
     }
 
     @Test
+    public void notifPositionAlignedWithClockAndBurnInOffsetInSplitShadeMode() {
+        setSplitShadeTopMargin(100); // this makes clock to be at 100
+        givenAOD();
+        mIsSplitShade = true;
+        givenMaxBurnInOffset(100);
+        givenHighestBurnInOffset(); // this makes clock to be at 200
+        // WHEN the position algorithm is run
+        positionClock();
+        // THEN the notif padding adjusts for burn-in offset: clock position - burn-in offset
+        assertThat(mClockPosition.stackScrollerPadding).isEqualTo(100);
+    }
+
+    @Test
     public void clockPositionedDependingOnMarginInSplitShade() {
         setSplitShadeTopMargin(400);
         givenLockScreen();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index cc4abfc..529519a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import org.junit.Before;
@@ -67,7 +68,8 @@
                 mStatusBarIconController,
                 mock(BatteryController.class),
                 mock(NavigationModeController.class),
-                mock(DumpManager.class));
+                mock(DumpManager.class),
+                new FakeDisplayTracker(mContext));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
index 189aa0f..0a68406 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
@@ -28,6 +28,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController.DarkIntensityApplier;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -56,7 +57,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mLightBarTransitionsController = new LightBarTransitionsController(mContext, mApplier,
-                new CommandQueue(mContext), mKeyguardStateController, mStatusBarStateController);
+                new CommandQueue(mContext, new FakeDisplayTracker(mContext)),
+                mKeyguardStateController, mStatusBarStateController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 64dee95..305b9fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.privacy.PrivacyItemController
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.screenrecord.RecordingController
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.BluetoothController
 import com.android.systemui.statusbar.policy.CastController
@@ -71,61 +72,37 @@
         private const val ALARM_SLOT = "alarm"
     }
 
-    @Mock
-    private lateinit var iconController: StatusBarIconController
-    @Mock
-    private lateinit var commandQueue: CommandQueue
-    @Mock
-    private lateinit var broadcastDispatcher: BroadcastDispatcher
-    @Mock
-    private lateinit var castController: CastController
-    @Mock
-    private lateinit var hotspotController: HotspotController
-    @Mock
-    private lateinit var bluetoothController: BluetoothController
-    @Mock
-    private lateinit var nextAlarmController: NextAlarmController
-    @Mock
-    private lateinit var userInfoController: UserInfoController
-    @Mock
-    private lateinit var rotationLockController: RotationLockController
-    @Mock
-    private lateinit var dataSaverController: DataSaverController
-    @Mock
-    private lateinit var zenModeController: ZenModeController
-    @Mock
-    private lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var locationController: LocationController
-    @Mock
-    private lateinit var sensorPrivacyController: SensorPrivacyController
-    @Mock
-    private lateinit var iActivityManager: IActivityManager
-    @Mock
-    private lateinit var alarmManager: AlarmManager
-    @Mock
-    private lateinit var userManager: UserManager
-    @Mock
-    private lateinit var devicePolicyManager: DevicePolicyManager
-    @Mock
-    private lateinit var recordingController: RecordingController
-    @Mock
-    private lateinit var telecomManager: TelecomManager
-    @Mock
-    private lateinit var sharedPreferences: SharedPreferences
-    @Mock
-    private lateinit var dateFormatUtil: DateFormatUtil
+    @Mock private lateinit var iconController: StatusBarIconController
+    @Mock private lateinit var commandQueue: CommandQueue
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var castController: CastController
+    @Mock private lateinit var hotspotController: HotspotController
+    @Mock private lateinit var bluetoothController: BluetoothController
+    @Mock private lateinit var nextAlarmController: NextAlarmController
+    @Mock private lateinit var userInfoController: UserInfoController
+    @Mock private lateinit var rotationLockController: RotationLockController
+    @Mock private lateinit var dataSaverController: DataSaverController
+    @Mock private lateinit var zenModeController: ZenModeController
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var locationController: LocationController
+    @Mock private lateinit var sensorPrivacyController: SensorPrivacyController
+    @Mock private lateinit var iActivityManager: IActivityManager
+    @Mock private lateinit var alarmManager: AlarmManager
+    @Mock private lateinit var userManager: UserManager
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+    @Mock private lateinit var recordingController: RecordingController
+    @Mock private lateinit var telecomManager: TelecomManager
+    @Mock private lateinit var sharedPreferences: SharedPreferences
+    @Mock private lateinit var dateFormatUtil: DateFormatUtil
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private lateinit var ringerModeTracker: RingerModeTracker
-    @Mock
-    private lateinit var privacyItemController: PrivacyItemController
-    @Mock
-    private lateinit var privacyLogger: PrivacyLogger
+    @Mock private lateinit var privacyItemController: PrivacyItemController
+    @Mock private lateinit var privacyLogger: PrivacyLogger
     @Captor
     private lateinit var alarmCallbackCaptor:
-            ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
+        ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback>
 
     private lateinit var executor: FakeExecutor
     private lateinit var statusBarPolicy: PhoneStatusBarPolicy
@@ -137,8 +114,8 @@
         executor = FakeExecutor(FakeSystemClock())
         testableLooper = TestableLooper.get(this)
         context.orCreateTestableResources.addOverride(
-                com.android.internal.R.string.status_bar_alarm_clock,
-                ALARM_SLOT
+            com.android.internal.R.string.status_bar_alarm_clock,
+            ALARM_SLOT
         )
         statusBarPolicy = createStatusBarPolicy()
     }
@@ -195,36 +172,37 @@
 
     private fun createStatusBarPolicy(): PhoneStatusBarPolicy {
         return PhoneStatusBarPolicy(
-                iconController,
-                commandQueue,
-                broadcastDispatcher,
-                executor,
-                testableLooper.looper,
-                context.resources,
-                castController,
-                hotspotController,
-                bluetoothController,
-                nextAlarmController,
-                userInfoController,
-                rotationLockController,
-                dataSaverController,
-                zenModeController,
-                deviceProvisionedController,
-                keyguardStateController,
-                locationController,
-                sensorPrivacyController,
-                iActivityManager,
-                alarmManager,
-                userManager,
-                devicePolicyManager,
-                recordingController,
-                telecomManager,
-                /* displayId = */ 0,
-                sharedPreferences,
-                dateFormatUtil,
-                ringerModeTracker,
-                privacyItemController,
-                privacyLogger
+            iconController,
+            commandQueue,
+            broadcastDispatcher,
+            executor,
+            testableLooper.looper,
+            context.resources,
+            castController,
+            hotspotController,
+            bluetoothController,
+            nextAlarmController,
+            userInfoController,
+            rotationLockController,
+            dataSaverController,
+            zenModeController,
+            deviceProvisionedController,
+            keyguardStateController,
+            locationController,
+            sensorPrivacyController,
+            iActivityManager,
+            alarmManager,
+            userManager,
+            userTracker,
+            devicePolicyManager,
+            recordingController,
+            telecomManager,
+            /* displayId = */ 0,
+            sharedPreferences,
+            dateFormatUtil,
+            ringerModeTracker,
+            privacyItemController,
+            privacyLogger
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index c7a0582..c0537a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -630,6 +630,7 @@
 
     @Test
     public void transitionToUnlocked() {
+        mScrimController.setClipsQsScrim(false);
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         finishAnimationsImmediately();
@@ -645,14 +646,21 @@
         ));
 
         // Back scrim should be visible after start dragging
-        mScrimController.setRawPanelExpansionFraction(0.3f);
+        mScrimController.setRawPanelExpansionFraction(0.29f);
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
                 mNotificationsScrim, TRANSPARENT,
                 mScrimBehind, SEMI_TRANSPARENT));
 
+        // Back scrim should be opaque at 30%
+        mScrimController.setRawPanelExpansionFraction(0.3f);
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, OPAQUE));
+
         // Then, notification scrim should fade in
-        mScrimController.setRawPanelExpansionFraction(0.7f);
+        mScrimController.setRawPanelExpansionFraction(0.31f);
         assertScrimAlpha(Map.of(
                 mScrimInFront, TRANSPARENT,
                 mNotificationsScrim, SEMI_TRANSPARENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index e3e648a..d8446f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
 import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
 
@@ -39,6 +38,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewRootImpl;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
 import android.window.WindowOnBackInvokedDispatcher;
@@ -56,10 +57,12 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.data.BouncerView;
 import com.android.systemui.keyguard.data.BouncerViewDelegate;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
@@ -105,7 +108,6 @@
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private View mNotificationContainer;
     @Mock private KeyguardBypassController mBypassController;
-    @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
     @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
     @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
     @Mock private KeyguardMessageArea mKeyguardMessageArea;
@@ -120,16 +122,20 @@
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock private BouncerView mBouncerView;
     @Mock private BouncerViewDelegate mBouncerViewDelegate;
+    @Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback;
 
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
+    private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
+            mBouncerExpansionCallback;
     private FakeKeyguardStateController mKeyguardStateController =
             spy(new FakeKeyguardStateController());
 
-    @Mock private ViewRootImpl mViewRootImpl;
-    @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+    @Mock
+    private ViewRootImpl mViewRootImpl;
+    @Mock
+    private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
     @Captor
-    private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+    private ArgumentCaptor<OnBackInvokedCallback> mBackCallbackCaptor;
 
 
     @Before
@@ -140,8 +146,10 @@
         when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
                 .thenReturn(mKeyguardMessageAreaController);
         when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
-
-        when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(true);
+        when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
+        when(mFeatureFlags
+                .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM))
+                .thenReturn(true);
 
         mStatusBarKeyguardViewManager =
                 new StatusBarKeyguardViewManager(
@@ -157,7 +165,6 @@
                         mock(NotificationShadeWindowController.class),
                         mKeyguardStateController,
                         mock(NotificationMediaManager.class),
-                        mKeyguardBouncerFactory,
                         mKeyguardMessageAreaFactory,
                         Optional.of(mSysUiUnfoldComponent),
                         () -> mShadeController,
@@ -183,8 +190,8 @@
                 mNotificationContainer,
                 mBypassController);
         mStatusBarKeyguardViewManager.show(null);
-        ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
-                ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
+        ArgumentCaptor<PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
+                ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
         verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback(
                 callbackArgumentCaptor.capture());
         mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
@@ -193,7 +200,8 @@
     @Test
     public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
         OnDismissAction action = () -> false;
-        Runnable cancelAction = () -> {};
+        Runnable cancelAction = () -> {
+        };
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, false /* afterKeyguardGone */);
         verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction));
@@ -541,12 +549,12 @@
         mBouncerExpansionCallback.onVisibilityChanged(true);
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
-                mOnBackInvokedCallback.capture());
+                mBackCallbackCaptor.capture());
 
         /* verify that the same callback is unregistered when the bouncer becomes invisible */
         mBouncerExpansionCallback.onVisibilityChanged(false);
         verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
-                eq(mOnBackInvokedCallback.getValue()));
+                eq(mBackCallbackCaptor.getValue()));
     }
 
     @Test
@@ -555,18 +563,63 @@
         /* capture the predictive back callback during registration */
         verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
                 eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
-                mOnBackInvokedCallback.capture());
+                mBackCallbackCaptor.capture());
 
         when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
         /* invoke the back callback directly */
-        mOnBackInvokedCallback.getValue().onBackInvoked();
+        mBackCallbackCaptor.getValue().onBackInvoked();
 
         /* verify that the bouncer will be hidden as a result of the invocation */
         verify(mCentralSurfaces).setBouncerShowing(eq(false));
     }
 
     @Test
+    public void testPredictiveBackCallback_noBackAnimationForFullScreenBouncer() {
+        when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
+                .thenReturn(KeyguardSecurityModel.SecurityMode.SimPin);
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        /* capture the predictive back callback during registration */
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                mBackCallbackCaptor.capture());
+        assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback);
+
+        OnBackAnimationCallback backCallback =
+                (OnBackAnimationCallback) mBackCallbackCaptor.getValue();
+
+        BackEvent event = new BackEvent(0, 0, 0, BackEvent.EDGE_LEFT);
+        backCallback.onBackStarted(event);
+        verify(mBouncerViewDelegateBackCallback, never()).onBackStarted(any());
+    }
+
+    @Test
+    public void testPredictiveBackCallback_forwardsBackDispatches() {
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        /* capture the predictive back callback during registration */
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                mBackCallbackCaptor.capture());
+        assertTrue(mBackCallbackCaptor.getValue() instanceof OnBackAnimationCallback);
+
+        OnBackAnimationCallback backCallback =
+                (OnBackAnimationCallback) mBackCallbackCaptor.getValue();
+
+        BackEvent event = new BackEvent(0, 0, 0, BackEvent.EDGE_LEFT);
+        backCallback.onBackStarted(event);
+        verify(mBouncerViewDelegateBackCallback).onBackStarted(eq(event));
+
+        backCallback.onBackProgressed(event);
+        verify(mBouncerViewDelegateBackCallback).onBackProgressed(eq(event));
+
+        backCallback.onBackInvoked();
+        verify(mBouncerViewDelegateBackCallback).onBackInvoked();
+
+        backCallback.onBackCancelled();
+        verify(mBouncerViewDelegateBackCallback).onBackCancelled();
+    }
+
+    @Test
     public void testReportBouncerOnDreamWhenVisible() {
         mBouncerExpansionCallback.onVisibilityChanged(true);
         verify(mCentralSurfaces).setBouncerShowingOverDream(false);
@@ -602,7 +655,6 @@
                         mock(NotificationShadeWindowController.class),
                         mKeyguardStateController,
                         mock(NotificationMediaManager.class),
-                        mKeyguardBouncerFactory,
                         mKeyguardMessageAreaFactory,
                         Optional.of(mSysUiUnfoldComponent),
                         () -> mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
deleted file mode 100644
index 0605b8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
+++ /dev/null
@@ -1,579 +0,0 @@
-/*
- * Copyright (C) 2018 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.systemui.statusbar.phone;
-
-import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewRootImpl;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
-import android.window.WindowOnBackInvokedDispatcher;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.util.LatencyTracker;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardMessageArea;
-import com.android.keyguard.KeyguardMessageAreaController;
-import com.android.keyguard.KeyguardSecurityModel;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyguard.data.BouncerView;
-import com.android.systemui.keyguard.data.BouncerViewDelegate;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
-import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.unfold.SysUIUnfoldComponent;
-
-import com.google.common.truth.Truth;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-/**
- * StatusBarKeyguardViewManager Test with deprecated KeyguardBouncer.java.
- * TODO: Delete when deleting {@link KeyguardBouncer}
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase {
-    private static final ShadeExpansionChangeEvent EXPANSION_EVENT =
-            expansionEvent(/* fraction= */ 0.5f, /* expanded= */ false, /* tracking= */ true);
-
-    @Mock private ViewMediatorCallback mViewMediatorCallback;
-    @Mock private LockPatternUtils mLockPatternUtils;
-    @Mock private CentralSurfaces mCentralSurfaces;
-    @Mock private ViewGroup mContainer;
-    @Mock private NotificationPanelViewController mNotificationPanelView;
-    @Mock private BiometricUnlockController mBiometricUnlockController;
-    @Mock private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock private View mNotificationContainer;
-    @Mock private KeyguardBypassController mBypassController;
-    @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
-    @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
-    @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
-    @Mock private KeyguardBouncer mPrimaryBouncer;
-    @Mock private KeyguardMessageArea mKeyguardMessageArea;
-    @Mock private ShadeController mShadeController;
-    @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
-    @Mock private DreamOverlayStateController mDreamOverlayStateController;
-    @Mock private LatencyTracker mLatencyTracker;
-    @Mock private FeatureFlags mFeatureFlags;
-    @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
-    @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
-    @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
-    @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
-    @Mock private BouncerView mBouncerView;
-    @Mock private BouncerViewDelegate mBouncerViewDelegate;
-
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
-    private FakeKeyguardStateController mKeyguardStateController =
-            spy(new FakeKeyguardStateController());
-
-    @Mock private ViewRootImpl mViewRootImpl;
-    @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
-    @Captor
-    private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
-
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mKeyguardBouncerFactory.create(
-                any(ViewGroup.class),
-                any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
-                .thenReturn(mPrimaryBouncer);
-        when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
-        when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
-        when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
-                .thenReturn(mKeyguardMessageAreaController);
-        when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
-
-        mStatusBarKeyguardViewManager =
-                new StatusBarKeyguardViewManager(
-                        getContext(),
-                        mViewMediatorCallback,
-                        mLockPatternUtils,
-                        mStatusBarStateController,
-                        mock(ConfigurationController.class),
-                        mKeyguardUpdateMonitor,
-                        mDreamOverlayStateController,
-                        mock(NavigationModeController.class),
-                        mock(DockManager.class),
-                        mock(NotificationShadeWindowController.class),
-                        mKeyguardStateController,
-                        mock(NotificationMediaManager.class),
-                        mKeyguardBouncerFactory,
-                        mKeyguardMessageAreaFactory,
-                        Optional.of(mSysUiUnfoldComponent),
-                        () -> mShadeController,
-                        mLatencyTracker,
-                        mKeyguardSecurityModel,
-                        mFeatureFlags,
-                        mPrimaryBouncerCallbackInteractor,
-                        mPrimaryBouncerInteractor,
-                        mBouncerView,
-                        mAlternateBouncerInteractor) {
-                    @Override
-                    public ViewRootImpl getViewRootImpl() {
-                        return mViewRootImpl;
-                    }
-                };
-        when(mViewRootImpl.getOnBackInvokedDispatcher())
-                .thenReturn(mOnBackInvokedDispatcher);
-        mStatusBarKeyguardViewManager.registerCentralSurfaces(
-                mCentralSurfaces,
-                mNotificationPanelView,
-                new ShadeExpansionStateManager(),
-                mBiometricUnlockController,
-                mNotificationContainer,
-                mBypassController);
-        mStatusBarKeyguardViewManager.show(null);
-        ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
-                ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
-        verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
-                callbackArgumentCaptor.capture());
-        mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
-    }
-
-    @Test
-    public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
-        OnDismissAction action = () -> false;
-        Runnable cancelAction = () -> {};
-        mStatusBarKeyguardViewManager.dismissWithAction(
-                action, cancelAction, false /* afterKeyguardGone */);
-        verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
-    }
-
-    @Test
-    public void showBouncer_onlyWhenShowing() {
-        mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
-        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
-        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
-        verify(mPrimaryBouncer, never()).show(anyBoolean());
-    }
-
-    @Test
-    public void showBouncer_notWhenBouncerAlreadyShowing() {
-        mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
-        when(mPrimaryBouncer.isSecure()).thenReturn(true);
-        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
-        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
-        verify(mPrimaryBouncer, never()).show(anyBoolean());
-    }
-
-    @Test
-    public void showBouncer_showsTheBouncer() {
-        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
-        verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
-    }
-
-    @Test
-    public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
-        when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
-    }
-
-    @Test
-    public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
-
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
-                expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
-        verify(mPrimaryBouncer).setExpansion(eq(0.6f));
-    }
-
-    @Test
-    public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer).setExpansion(eq(0.5f));
-
-        reset(mPrimaryBouncer);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
-    }
-
-    @Test
-    public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
-        mStatusBarKeyguardViewManager.hide(0, 0);
-        when(mPrimaryBouncer.inTransit()).thenReturn(true);
-
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer).setExpansion(eq(EXPANSION_HIDDEN));
-    }
-
-    @Test
-    public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
-        mKeyguardStateController.setCanDismissLockScreen(false);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer).show(eq(false), eq(false));
-
-        // But not when it's already visible
-        reset(mPrimaryBouncer);
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
-
-        // Or animating away
-        reset(mPrimaryBouncer);
-        when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
-    }
-
-    @Test
-    public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
-        when(mBiometricUnlockController.getMode())
-                .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
-                expansionEvent(
-                        /* fraction= */ EXPANSION_VISIBLE,
-                        /* expanded= */ true,
-                        /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
-    }
-
-    @Test
-    public void onPanelExpansionChanged_neverTranslatesBouncerWhenDismissBouncer() {
-        // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
-        // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
-        // which would mistakenly cause the bouncer to show briefly before its visibility
-        // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
-        // bouncer if the bouncer is dismissing as a result of a biometric unlock.
-        when(mBiometricUnlockController.getMode())
-                .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
-                expansionEvent(
-                        /* fraction= */ EXPANSION_VISIBLE,
-                        /* expanded= */ true,
-                        /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
-    }
-
-    @Test
-    public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
-        when(mKeyguardStateController.isOccluded()).thenReturn(true);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
-                expansionEvent(
-                        /* fraction= */ EXPANSION_VISIBLE,
-                        /* expanded= */ true,
-                        /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
-    }
-
-    @Test
-    public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
-        // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
-        // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
-        // which would mistakenly cause the bouncer to show briefly before its visibility
-        // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
-        // bouncer if the bouncer is dismissing as a result of a biometric unlock.
-        when(mBiometricUnlockController.getMode())
-                .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
-                expansionEvent(
-                        /* fraction= */ EXPANSION_VISIBLE,
-                        /* expanded= */ true,
-                        /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
-    }
-
-    @Test
-    public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() {
-        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
-        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
-                expansionEvent(
-                        /* fraction= */ EXPANSION_VISIBLE,
-                        /* expanded= */ true,
-                        /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
-    }
-
-    @Test
-    public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() {
-        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
-        verify(mCentralSurfaces).animateKeyguardUnoccluding();
-
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
-        clearInvocations(mCentralSurfaces);
-        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
-        verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
-    }
-
-    @Test
-    public void setOccluded_onKeyguardOccludedChangedCalled() {
-        clearInvocations(mKeyguardStateController);
-        clearInvocations(mKeyguardUpdateMonitor);
-
-        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
-        verify(mKeyguardStateController).notifyKeyguardState(true, false);
-
-        clearInvocations(mKeyguardUpdateMonitor);
-        clearInvocations(mKeyguardStateController);
-
-        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
-        verify(mKeyguardStateController).notifyKeyguardState(true, true);
-
-        clearInvocations(mKeyguardUpdateMonitor);
-        clearInvocations(mKeyguardStateController);
-
-        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
-        verify(mKeyguardStateController).notifyKeyguardState(true, false);
-    }
-
-    @Test
-    public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
-        mStatusBarKeyguardViewManager.show(null);
-
-        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
-        verify(mKeyguardStateController).notifyKeyguardState(true, true);
-    }
-
-    @Test
-    public void setOccluded_isLaunchingActivityOverLockscreen_onKeyguardOccludedChangedCalled() {
-        when(mCentralSurfaces.isLaunchingActivityOverLockscreen()).thenReturn(true);
-        mStatusBarKeyguardViewManager.show(null);
-
-        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
-        verify(mKeyguardStateController).notifyKeyguardState(true, true);
-    }
-
-    @Test
-    public void testHiding_cancelsGoneRunnable() {
-        OnDismissAction action = mock(OnDismissAction.class);
-        Runnable cancelAction = mock(Runnable.class);
-        mStatusBarKeyguardViewManager.dismissWithAction(
-                action, cancelAction, true /* afterKeyguardGone */);
-
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
-        mStatusBarKeyguardViewManager.hideBouncer(true);
-        mStatusBarKeyguardViewManager.hide(0, 30);
-        verify(action, never()).onDismiss();
-        verify(cancelAction).run();
-    }
-
-    @Test
-    public void testHidingBouncer_cancelsGoneRunnable() {
-        OnDismissAction action = mock(OnDismissAction.class);
-        Runnable cancelAction = mock(Runnable.class);
-        mStatusBarKeyguardViewManager.dismissWithAction(
-                action, cancelAction, true /* afterKeyguardGone */);
-
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
-        mStatusBarKeyguardViewManager.hideBouncer(true);
-
-        verify(action, never()).onDismiss();
-        verify(cancelAction).run();
-    }
-
-    @Test
-    public void testHiding_doesntCancelWhenShowing() {
-        OnDismissAction action = mock(OnDismissAction.class);
-        Runnable cancelAction = mock(Runnable.class);
-        mStatusBarKeyguardViewManager.dismissWithAction(
-                action, cancelAction, true /* afterKeyguardGone */);
-
-        mStatusBarKeyguardViewManager.hide(0, 30);
-        verify(action).onDismiss();
-        verify(cancelAction, never()).run();
-    }
-
-    @Test
-    public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
-        when(mPrimaryBouncer.inTransit()).thenReturn(true);
-
-        assertTrue(
-                "Is or will be showing should be true when bouncer is in transit",
-                mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
-    }
-
-    @Test
-    public void testUpdateResources_delegatesToBouncer() {
-        mStatusBarKeyguardViewManager.updateResources();
-
-        verify(mPrimaryBouncer).updateResources();
-    }
-
-    @Test
-    public void updateKeyguardPosition_delegatesToBouncer() {
-        mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
-
-        verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
-    }
-
-    @Test
-    public void testIsBouncerInTransit() {
-        when(mPrimaryBouncer.inTransit()).thenReturn(true);
-        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
-        when(mPrimaryBouncer.inTransit()).thenReturn(false);
-        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
-        mPrimaryBouncer = null;
-        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
-    }
-
-    private static ShadeExpansionChangeEvent expansionEvent(
-            float fraction, boolean expanded, boolean tracking) {
-        return new ShadeExpansionChangeEvent(
-                fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
-    }
-
-    @Test
-    public void testPredictiveBackCallback_registration() {
-        /* verify that a predictive back callback is registered when the bouncer becomes visible */
-        mBouncerExpansionCallback.onVisibilityChanged(true);
-        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
-                mOnBackInvokedCallback.capture());
-
-        /* verify that the same callback is unregistered when the bouncer becomes invisible */
-        mBouncerExpansionCallback.onVisibilityChanged(false);
-        verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
-                eq(mOnBackInvokedCallback.getValue()));
-    }
-
-    @Test
-    public void testPredictiveBackCallback_invocationHidesBouncer() {
-        mBouncerExpansionCallback.onVisibilityChanged(true);
-        /* capture the predictive back callback during registration */
-        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
-                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
-                mOnBackInvokedCallback.capture());
-
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
-        when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
-        /* invoke the back callback directly */
-        mOnBackInvokedCallback.getValue().onBackInvoked();
-
-        /* verify that the bouncer will be hidden as a result of the invocation */
-        verify(mCentralSurfaces).setBouncerShowing(eq(false));
-    }
-
-    @Test
-    public void testReportBouncerOnDreamWhenVisible() {
-        mBouncerExpansionCallback.onVisibilityChanged(true);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
-        Mockito.clearInvocations(mCentralSurfaces);
-        when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
-        mBouncerExpansionCallback.onVisibilityChanged(true);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(true);
-    }
-
-    @Test
-    public void testReportBouncerOnDreamWhenNotVisible() {
-        mBouncerExpansionCallback.onVisibilityChanged(false);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
-        Mockito.clearInvocations(mCentralSurfaces);
-        when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
-        mBouncerExpansionCallback.onVisibilityChanged(false);
-        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
-    }
-
-    @Test
-    public void flag_off_DoesNotCallBouncerInteractor() {
-        when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
-        mStatusBarKeyguardViewManager.hideBouncer(false);
-        verify(mPrimaryBouncerInteractor, never()).hide();
-    }
-
-    @Test
-    public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
-        mStatusBarKeyguardViewManager =
-                new StatusBarKeyguardViewManager(
-                        getContext(),
-                        mViewMediatorCallback,
-                        mLockPatternUtils,
-                        mStatusBarStateController,
-                        mock(ConfigurationController.class),
-                        mKeyguardUpdateMonitor,
-                        mDreamOverlayStateController,
-                        mock(NavigationModeController.class),
-                        mock(DockManager.class),
-                        mock(NotificationShadeWindowController.class),
-                        mKeyguardStateController,
-                        mock(NotificationMediaManager.class),
-                        mKeyguardBouncerFactory,
-                        mKeyguardMessageAreaFactory,
-                        Optional.of(mSysUiUnfoldComponent),
-                        () -> mShadeController,
-                        mLatencyTracker,
-                        mKeyguardSecurityModel,
-                        mFeatureFlags,
-                        mPrimaryBouncerCallbackInteractor,
-                        mPrimaryBouncerInteractor,
-                        mBouncerView,
-                        mAlternateBouncerInteractor) {
-                    @Override
-                    public ViewRootImpl getViewRootImpl() {
-                        return mViewRootImpl;
-                    }
-                };
-
-        // the following call before registering centralSurfaces should NOT throw a NPE:
-        mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 19658e6..ccc57ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -59,6 +60,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeControllerImpl;
@@ -139,6 +141,8 @@
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock
     private InteractionJankMonitor mJankMonitor;
+    @Mock
+    private UserTracker mUserTracker;
     private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private ExpandableNotificationRow mNotificationRow;
     private ExpandableNotificationRow mBubbleNotificationRow;
@@ -183,6 +187,8 @@
         when(mVisibilityProvider.obtain(any(NotificationEntry.class), anyBoolean()))
                 .thenAnswer(invocation -> NotificationVisibility.obtain(
                         invocation.<NotificationEntry>getArgument(0).getKey(), 0, 1, false));
+        when(mUserTracker.getUserHandle()).thenReturn(
+                UserHandle.of(ActivityManager.getCurrentUser()));
 
         HeadsUpManagerPhone headsUpManager = mock(HeadsUpManagerPhone.class);
         NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
@@ -222,7 +228,8 @@
                         mActivityLaunchAnimator,
                         notificationAnimationProvider,
                         mock(LaunchFullScreenIntentProvider.class),
-                        mock(FeatureFlags.class)
+                        mock(FeatureFlags.class),
+                        mUserTracker
                 );
 
         // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index eef43bd..8841521 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -40,6 +40,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shade.NotificationPanelViewController;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
@@ -92,7 +93,7 @@
         mMetricsLogger = new FakeMetricsLogger();
         LockscreenGestureLogger lockscreenGestureLogger = new LockscreenGestureLogger(
                 mMetricsLogger);
-        mCommandQueue = new CommandQueue(mContext);
+        mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
         mDependency.injectTestDependency(StatusBarStateController.class,
                 mock(SysuiStatusBarStateController.class));
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 613238f..929099a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -32,6 +32,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
@@ -78,7 +79,8 @@
         mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
                 mock(GroupExpansionManager.class), mNotificationLockscreenUserManager,
                 mKeyguardStateController, mStatusBarStateController, mStatusBarKeyguardViewManager,
-                mActivityStarter, mShadeController, new CommandQueue(mContext),
+                mActivityStarter, mShadeController,
+                new CommandQueue(mContext, new FakeDisplayTracker(mContext)),
                 mock(ActionClickLogger.class), mFakeExecutor));
         mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 438271c..07e8d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -23,10 +23,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -45,6 +47,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.dump.DumpManager;
@@ -58,7 +61,6 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DisableFlagsLogger;
 import com.android.systemui.statusbar.OperatorNameViewController;
-import com.android.systemui.statusbar.connectivity.NetworkController;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -68,6 +70,8 @@
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
 import com.android.systemui.util.CarrierConfigTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
@@ -80,6 +84,9 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -92,7 +99,6 @@
     private StatusBarLocationPublisher mLocationPublisher;
     // Set in instantiate()
     private StatusBarIconController mStatusBarIconController;
-    private NetworkController mNetworkController;
     private KeyguardStateController mKeyguardStateController;
 
     private final CommandQueue mCommandQueue = mock(CommandQueue.class);
@@ -120,6 +126,12 @@
     private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private StatusBarWindowStateController mStatusBarWindowStateController;
+    @Mock
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+    private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
 
     public CollapsedStatusBarFragmentTest() {
         super(CollapsedStatusBarFragment.class);
@@ -129,6 +141,14 @@
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         mDependency.injectMockDependency(DarkIconDispatcher.class);
+
+        // Keep the window state listeners so we can dispatch to them to test the status bar
+        // fragment's response.
+        doAnswer(invocation -> {
+            mStatusBarWindowStateListeners.add(invocation.getArgument(0));
+            return null;
+        }).when(mStatusBarWindowStateController).addListener(
+                any(StatusBarWindowStateListener.class));
     }
 
     @Test
@@ -416,6 +436,27 @@
         assertFalse(contains);
     }
 
+    @Test
+    public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
+        final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        mockSecureCameraLaunch(fragment, true /* launched */);
+
+        // Status icons should be invisible or gone, but certainly not VISIBLE.
+        assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+        mockSecureCameraLaunchFinished();
+
+        assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+        mockSecureCameraLaunch(fragment, false /* launched */);
+
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertEquals(View.VISIBLE, getClockView().getVisibility());
+    }
+
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         MockitoAnnotations.initMocks(this);
@@ -424,7 +465,6 @@
         mAnimationScheduler = mock(SystemStatusAnimationScheduler.class);
         mLocationPublisher = mock(StatusBarLocationPublisher.class);
         mStatusBarIconController = mock(StatusBarIconController.class);
-        mNetworkController = mock(NetworkController.class);
         mStatusBarStateController = mock(StatusBarStateController.class);
         mKeyguardStateController = mock(KeyguardStateController.class);
         mOperatorNameViewController = mock(OperatorNameViewController.class);
@@ -448,7 +488,6 @@
                 mStatusBarHideIconsForBouncerManager,
                 mKeyguardStateController,
                 mNotificationPanelViewController,
-                mNetworkController,
                 mStatusBarStateController,
                 mCommandQueue,
                 mCarrierConfigTracker,
@@ -459,7 +498,9 @@
                 mOperatorNameViewControllerFactory,
                 mSecureSettings,
                 mExecutor,
-                mDumpManager);
+                mDumpManager,
+                mStatusBarWindowStateController,
+                mKeyguardUpdateMonitor);
     }
 
     private void setUpDaggerComponent() {
@@ -482,6 +523,35 @@
                 mNotificationAreaInner);
     }
 
+    /**
+     * Configure mocks to return values consistent with the secure camera animating itself launched
+     * over the keyguard.
+     */
+    private void mockSecureCameraLaunch(CollapsedStatusBarFragment fragment, boolean launched) {
+        when(mKeyguardUpdateMonitor.isSecureCameraLaunchedOverKeyguard()).thenReturn(launched);
+        when(mKeyguardStateController.isOccluded()).thenReturn(launched);
+
+        if (launched) {
+            fragment.onCameraLaunchGestureDetected(0 /* source */);
+        } else {
+            for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+                listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_SHOWING);
+            }
+        }
+
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+    }
+
+    /**
+     * Configure mocks to return values consistent with the secure camera showing over the keyguard
+     * with its launch animation finished.
+     */
+    private void mockSecureCameraLaunchFinished() {
+        for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+            listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_HIDDEN);
+        }
+    }
+
     private CollapsedStatusBarFragment resumeAndGetFragment() {
         mFragments.dispatchResume();
         processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index d30222f..711e4ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -50,7 +50,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.*
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.nullable
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.eq
@@ -83,7 +85,8 @@
     private lateinit var notifCollectionListener: NotifCollectionListener
 
     @Mock private lateinit var mockOngoingCallFlags: OngoingCallFlags
-    @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
+    @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler:
+        SwipeStatusBarAwayGestureHandler
     @Mock private lateinit var mockOngoingCallListener: OngoingCallListener
     @Mock private lateinit var mockActivityStarter: ActivityStarter
     @Mock private lateinit var mockIActivityManager: IActivityManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 49d4bdc..0add905e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
@@ -56,6 +57,10 @@
 
     private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
     override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
+    override val activeSubChangedInGroupEvent: MutableSharedFlow<Unit> = MutableSharedFlow()
+
+    private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+    override val defaultDataSubId = _defaultDataSubId
 
     private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
     override val defaultMobileNetworkConnectivity = _mobileConnectivity
@@ -81,6 +86,10 @@
         _subscriptions.value = subs
     }
 
+    fun setDefaultDataSubId(id: Int) {
+        _defaultDataSubId.value = id
+    }
+
     fun setMobileConnectivity(model: MobileConnectivityModel) {
         _mobileConnectivity.value = model
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 9d16b7fe..f12d113 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -90,6 +90,14 @@
     }
 
     @Test
+    fun `connectivity - defaults to connected and validated`() =
+        testScope.runTest {
+            val connectivity = underTest.defaultMobileNetworkConnectivity.value
+            assertThat(connectivity.isConnected).isTrue()
+            assertThat(connectivity.isValidated).isTrue()
+        }
+
+    @Test
     fun `network event - create new subscription`() =
         testScope.runTest {
             var latest: List<SubscriptionModel>? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index d6b8c0d..314e250 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -41,6 +41,8 @@
 import android.telephony.TelephonyManager.DATA_CONNECTING
 import android.telephony.TelephonyManager.DATA_DISCONNECTED
 import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_HANDOVER_IN_PROGRESS
+import android.telephony.TelephonyManager.DATA_SUSPENDED
 import android.telephony.TelephonyManager.DATA_UNKNOWN
 import android.telephony.TelephonyManager.ERI_OFF
 import android.telephony.TelephonyManager.ERI_ON
@@ -255,6 +257,37 @@
         }
 
     @Test
+    fun testFlowForSubId_dataConnectionState_suspended() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            val callback =
+                getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(DATA_SUSPENDED, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Suspended)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_dataConnectionState_handoverInProgress() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            val callback =
+                getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(DATA_HANDOVER_IN_PROGRESS, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState)
+                .isEqualTo(DataConnectionState.HandoverInProgress)
+
+            job.cancel()
+        }
+
+    @Test
     fun testFlowForSubId_dataConnectionState_unknown() =
         runBlocking(IMMEDIATE) {
             var latest: MobileConnectionModel? = null
@@ -270,6 +303,21 @@
         }
 
     @Test
+    fun testFlowForSubId_dataConnectionState_invalid() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            val callback =
+                getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(45, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Invalid)
+
+            job.cancel()
+        }
+
+    @Test
     fun testFlowForSubId_dataActivity() =
         runBlocking(IMMEDIATE) {
             var latest: MobileConnectionModel? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 813b0ed..db8172a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -22,6 +22,7 @@
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.os.ParcelUuid
 import android.provider.Settings
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionInfo
@@ -50,6 +51,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
+import java.util.UUID
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -104,6 +106,17 @@
             mock<TableLogBuffer>()
         }
 
+        // For convenience, set up the subscription info callbacks
+        whenever(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenAnswer { invocation ->
+            when (invocation.getArgument(0) as Int) {
+                1 -> SUB_1
+                2 -> SUB_2
+                3 -> SUB_3
+                4 -> SUB_4
+                else -> null
+            }
+        }
+
         wifiRepository = FakeWifiRepository()
 
         connectionFactory =
@@ -479,6 +492,35 @@
         }
 
     @Test
+    fun testDefaultDataSubId_updatesOnBroadcast() =
+        runBlocking(IMMEDIATE) {
+            var latest: Int? = null
+            val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(
+                    context,
+                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+                )
+            }
+
+            assertThat(latest).isEqualTo(SUB_2_ID)
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(
+                    context,
+                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+                )
+            }
+
+            assertThat(latest).isEqualTo(SUB_1_ID)
+
+            job.cancel()
+        }
+
+    @Test
     fun mobileConnectivity_default() {
         assertThat(underTest.defaultMobileNetworkConnectivity.value)
             .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
@@ -657,6 +699,38 @@
             job.cancel()
         }
 
+    @Test
+    fun `active data change - in same group - emits unit`() =
+        runBlocking(IMMEDIATE) {
+            var latest: Unit? = null
+            val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
+
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED)
+
+            assertThat(latest).isEqualTo(Unit)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `active data change - not in same group - does not emit`() =
+        runBlocking(IMMEDIATE) {
+            var latest: Unit? = null
+            val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
+
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_1_ID)
+
+            assertThat(latest).isEqualTo(null)
+
+            job.cancel()
+        }
+
     private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
         mock<NetworkCapabilities>().also {
             whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
@@ -690,19 +764,50 @@
 
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
+
+        // Subscription 1
         private const val SUB_1_ID = 1
         private val SUB_1 =
-            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+            mock<SubscriptionInfo>().also {
+                whenever(it.subscriptionId).thenReturn(SUB_1_ID)
+                whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID()))
+            }
         private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
 
+        // Subscription 2
         private const val SUB_2_ID = 2
         private val SUB_2 =
-            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+            mock<SubscriptionInfo>().also {
+                whenever(it.subscriptionId).thenReturn(SUB_2_ID)
+                whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID()))
+            }
         private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
 
+        // Subs 3 and 4 are considered to be in the same group ------------------------------------
+        private val GROUP_ID_3_4 = ParcelUuid(UUID.randomUUID())
+
+        // Subscription 3
+        private const val SUB_3_ID_GROUPED = 3
+        private val SUB_3 =
+            mock<SubscriptionInfo>().also {
+                whenever(it.subscriptionId).thenReturn(SUB_3_ID_GROUPED)
+                whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
+            }
+
+        // Subscription 4
+        private const val SUB_4_ID_GROUPED = 4
+        private val SUB_4 =
+            mock<SubscriptionInfo>().also {
+                whenever(it.subscriptionId).thenReturn(SUB_4_ID_GROUPED)
+                whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
+            }
+
+        // Subs 3 and 4 are considered to be in the same group ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
         private const val NET_ID = 123
         private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
 
+        // Carrier merged subscription
         private const val SUB_CM_ID = 5
         private val SUB_CM =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index a29146b..7aeaa48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -40,6 +40,8 @@
             )
         )
 
+    override val isConnected = MutableStateFlow(true)
+
     private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
     override val networkTypeIconGroup = _iconGroup
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 1c00646..172755c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -59,6 +60,9 @@
     override val alwaysShowDataRatIcon = MutableStateFlow(false)
 
     override val alwaysUseCdmaLevel = MutableStateFlow(false)
+    override val defaultDataSubId = MutableStateFlow(DEFAULT_DATA_SUB_ID)
+
+    override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
 
     private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
     override val defaultMobileIconMapping = _defaultMobileIconMapping
@@ -77,6 +81,8 @@
     companion object {
         val DEFAULT_ICON = TelephonyIcons.G
 
+        const val DEFAULT_DATA_SUB_ID = 1
+
         // Use [MobileMappings] to define some simple definitions
         const val THREE_G = NETWORK_TYPE_GSM
         const val LTE = NETWORK_TYPE_LTE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index e6be7f1..c42aba5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -61,8 +61,10 @@
                 mobileIconsInteractor.activeDataConnectionHasDataEnabled,
                 mobileIconsInteractor.alwaysShowDataRatIcon,
                 mobileIconsInteractor.alwaysUseCdmaLevel,
+                mobileIconsInteractor.defaultMobileNetworkConnectivity,
                 mobileIconsInteractor.defaultMobileIconMapping,
                 mobileIconsInteractor.defaultMobileIconGroup,
+                mobileIconsInteractor.defaultDataSubId,
                 mobileIconsInteractor.isDefaultConnectionFailed,
                 connectionRepository,
             )
@@ -289,6 +291,30 @@
         }
 
     @Test
+    fun `icon group - checks default data`() =
+        runBlocking(IMMEDIATE) {
+            mobileIconsInteractor.defaultDataSubId.value = SUB_1_ID
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
+                ),
+            )
+
+            var latest: MobileIconGroup? = null
+            val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(TelephonyIcons.THREE_G)
+
+            // Default data sub id changes to something else
+            mobileIconsInteractor.defaultDataSubId.value = 123
+            yield()
+
+            assertThat(latest).isEqualTo(TelephonyIcons.NOT_DEFAULT_DATA)
+
+            job.cancel()
+        }
+
+    @Test
     fun alwaysShowDataRatIcon_matchesParent() =
         runBlocking(IMMEDIATE) {
             var latest: Boolean? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index b82a584..bd24922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -28,14 +28,17 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.yield
 import org.junit.After
 import org.junit.Before
@@ -43,13 +46,16 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class MobileIconsInteractorTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconsInteractor
     private lateinit var connectionsRepository: FakeMobileConnectionsRepository
     private val userSetupRepository = FakeUserSetupRepository()
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
-    private val scope = CoroutineScope(IMMEDIATE)
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
 
@@ -72,8 +78,9 @@
             MobileIconsInteractorImpl(
                 connectionsRepository,
                 carrierConfigTracker,
+                logger = mock(),
                 userSetupRepository,
-                scope
+                testScope.backgroundScope,
             )
     }
 
@@ -81,7 +88,7 @@
 
     @Test
     fun filteredSubscriptions_default() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: List<SubscriptionModel>? = null
             val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
 
@@ -92,7 +99,7 @@
 
     @Test
     fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
 
             var latest: List<SubscriptionModel>? = null
@@ -105,7 +112,7 @@
 
     @Test
     fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
@@ -122,7 +129,7 @@
 
     @Test
     fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
@@ -139,7 +146,7 @@
 
     @Test
     fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
@@ -157,7 +164,7 @@
 
     @Test
     fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
@@ -175,7 +182,7 @@
 
     @Test
     fun activeDataConnection_turnedOn() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             CONNECTION_1.setDataEnabled(true)
             var latest: Boolean? = null
             val job =
@@ -188,7 +195,7 @@
 
     @Test
     fun activeDataConnection_turnedOff() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             CONNECTION_1.setDataEnabled(true)
             var latest: Boolean? = null
             val job =
@@ -204,7 +211,7 @@
 
     @Test
     fun activeDataConnection_invalidSubId() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job =
                 underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
@@ -220,7 +227,7 @@
 
     @Test
     fun failedConnection_connected_validated_notFailed() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
             connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true))
@@ -233,7 +240,7 @@
 
     @Test
     fun failedConnection_notConnected_notValidated_notFailed() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
 
@@ -247,7 +254,7 @@
 
     @Test
     fun failedConnection_connected_notValidated_failed() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
 
@@ -261,7 +268,7 @@
 
     @Test
     fun alwaysShowDataRatIcon_configHasTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
 
@@ -277,7 +284,7 @@
 
     @Test
     fun alwaysShowDataRatIcon_configHasFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this)
 
@@ -293,7 +300,7 @@
 
     @Test
     fun alwaysUseCdmaLevel_configHasTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
 
@@ -309,7 +316,7 @@
 
     @Test
     fun alwaysUseCdmaLevel_configHasFalse() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this)
 
@@ -323,8 +330,286 @@
             job.cancel()
         }
 
+    @Test
+    fun `default mobile connectivity - uses repo value`() =
+        testScope.runTest {
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            var expected = MobileConnectivityModel(isConnected = true, isValidated = true)
+            connectionsRepository.setMobileConnectivity(expected)
+            assertThat(latest).isEqualTo(expected)
+
+            expected = MobileConnectivityModel(isConnected = false, isValidated = true)
+            connectionsRepository.setMobileConnectivity(expected)
+            assertThat(latest).isEqualTo(expected)
+
+            expected = MobileConnectivityModel(isConnected = true, isValidated = false)
+            connectionsRepository.setMobileConnectivity(expected)
+            assertThat(latest).isEqualTo(expected)
+
+            expected = MobileConnectivityModel(isConnected = false, isValidated = false)
+            connectionsRepository.setMobileConnectivity(expected)
+            assertThat(latest).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `data switch - in same group - validated matches previous value`() =
+        testScope.runTest {
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = true,
+                    isValidated = true,
+                )
+            )
+            // Trigger a data change in the same subscription group
+            connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = false,
+                    isValidated = false,
+                )
+            )
+
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = true,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `data switch - in same group - validated matches previous value - expires after 2s`() =
+        testScope.runTest {
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = true,
+                    isValidated = true,
+                )
+            )
+            // Trigger a data change in the same subscription group
+            connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = false,
+                    isValidated = false,
+                )
+            )
+            // After 1s, the force validation bit is still present
+            advanceTimeBy(1000)
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = true,
+                    )
+                )
+
+            // After 2s, the force validation expires
+            advanceTimeBy(1001)
+
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = false,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `data switch - in same group - not validated - uses new value immediately`() =
+        testScope.runTest {
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = true,
+                    isValidated = false,
+                )
+            )
+            connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = false,
+                    isValidated = false,
+                )
+            )
+
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = false,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `data switch - lose validation - then switch happens - clears forced bit`() =
+        testScope.runTest {
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            // GIVEN the network starts validated
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = true,
+                    isValidated = true,
+                )
+            )
+
+            // WHEN a data change happens in the same group
+            connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+
+            // WHEN the validation bit is lost
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = false,
+                    isValidated = false,
+                )
+            )
+
+            // WHEN another data change happens in the same group
+            connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+
+            // THEN the forced validation bit is still removed after 2s
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = true,
+                    )
+                )
+
+            advanceTimeBy(1000)
+
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = true,
+                    )
+                )
+
+            advanceTimeBy(1001)
+
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = false,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `data switch - while already forcing validation - resets clock`() =
+        testScope.runTest {
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = true,
+                    isValidated = true,
+                )
+            )
+
+            connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+
+            advanceTimeBy(1000)
+
+            // WHEN another change in same group event happens
+            connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = false,
+                    isValidated = false,
+                )
+            )
+
+            // THEN the forced validation remains for exactly 2 more seconds from now
+
+            // 1.500s from second event
+            advanceTimeBy(1500)
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = true,
+                    )
+                )
+
+            // 2.001s from the second event
+            advanceTimeBy(501)
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = false,
+                    )
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun `data switch - not in same group - uses new values`() =
+        testScope.runTest {
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = true,
+                    isValidated = true,
+                )
+            )
+            connectionsRepository.setMobileConnectivity(
+                MobileConnectivityModel(
+                    isConnected = false,
+                    isValidated = false,
+                )
+            )
+
+            assertThat(latest)
+                .isEqualTo(
+                    MobileConnectivityModel(
+                        isConnected = false,
+                        isValidated = false,
+                    )
+                )
+
+            job.cancel()
+        }
+
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
         private val tableLogBuffer =
             TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 2a8d42f..b91a4df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
 import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
+import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
 import com.android.settingslib.graph.SignalDrawable
 import com.android.settingslib.mobile.TelephonyIcons.THREE_G
 import com.android.systemui.SysuiTestCase
@@ -122,6 +124,39 @@
         }
 
     @Test
+    fun contentDescription_notInService_usesNoPhone() =
+        testScope.runTest {
+            var latest: ContentDescription? = null
+            val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
+
+            interactor.isInService.value = false
+
+            assertThat((latest as ContentDescription.Resource).res)
+                .isEqualTo(PHONE_SIGNAL_STRENGTH_NONE)
+
+            job.cancel()
+        }
+
+    @Test
+    fun contentDescription_inService_usesLevel() =
+        testScope.runTest {
+            var latest: ContentDescription? = null
+            val job = underTest.contentDescription.onEach { latest = it }.launchIn(this)
+
+            interactor.isInService.value = true
+
+            interactor.level.value = 2
+            assertThat((latest as ContentDescription.Resource).res)
+                .isEqualTo(PHONE_SIGNAL_STRENGTH[2])
+
+            interactor.level.value = 0
+            assertThat((latest as ContentDescription.Resource).res)
+                .isEqualTo(PHONE_SIGNAL_STRENGTH[0])
+
+            job.cancel()
+        }
+
+    @Test
     fun networkType_dataEnabled_groupIsRepresented() =
         testScope.runTest {
             val expected =
@@ -274,6 +309,41 @@
         }
 
     @Test
+    fun `network type - alwaysShow - shown when not connected`() =
+        testScope.runTest {
+            interactor.setIconGroup(THREE_G)
+            interactor.isConnected.value = false
+            interactor.alwaysShowDataRatIcon.value = true
+
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            val expected =
+                Icon.Resource(
+                    THREE_G.dataType,
+                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                )
+            assertThat(latest).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `network type - not shown when not connected`() =
+        testScope.runTest {
+            interactor.setIconGroup(THREE_G)
+            interactor.isDataConnected.value = true
+            interactor.isConnected.value = false
+
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
     fun roaming() =
         testScope.runTest {
             interactor.isRoaming.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index b32058f..3dccbbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -45,7 +45,7 @@
 
     @Test
     fun testLogNetworkCapsChange_bufferHasInfo() {
-        logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS)
+        logger.logOnCapabilitiesChanged(NET_1, NET_1_CAPS, isDefaultNetworkCallback = true)
 
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -54,6 +54,7 @@
         val expectedNetId = NET_1_ID.toString()
         val expectedCaps = NET_1_CAPS.toString()
 
+        assertThat(actualString).contains("true")
         assertThat(actualString).contains(expectedNetId)
         assertThat(actualString).contains(expectedCaps)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index 3fe6983..e4c8fd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.view
 
+import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
@@ -118,6 +119,22 @@
         assertThat(view.isIconVisible).isEqualTo(false)
     }
 
+    @Test
+    fun getDrawingRect_takesTranslationIntoAccount() {
+        val view = createAndInitView()
+
+        view.translationX = 50f
+        view.translationY = 60f
+
+        val drawingRect = Rect()
+        view.getDrawingRect(drawingRect)
+
+        assertThat(drawingRect.left).isEqualTo(view.left + 50)
+        assertThat(drawingRect.right).isEqualTo(view.right + 50)
+        assertThat(drawingRect.top).isEqualTo(view.top + 60)
+        assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60)
+    }
+
     private fun createAndInitView(): ModernStatusBarView {
         val view = ModernStatusBarView(context, null)
         binding = TestBinding()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 87ce8fa..7099f1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -21,7 +21,10 @@
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_VPN
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.TransportInfo
+import android.net.VpnTransportInfo
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
@@ -243,6 +246,54 @@
         job.cancel()
     }
 
+    /** Regression test for b/266628069. */
+    @Test
+    fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.isWifiDefault.launchIn(this)
+
+            val transportInfo = VpnTransportInfo(
+                /* type= */ 0,
+                /* sessionId= */ "sessionId",
+            )
+            val networkCapabilities = mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+                whenever(it.transportInfo).thenReturn(transportInfo)
+            }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
+
+            assertThat(underTest.isWifiDefault.value).isFalse()
+
+            job.cancel()
+        }
+
+    /** Regression test for b/266628069. */
+    @Test
+    fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.isWifiDefault.launchIn(this)
+
+            val transportInfo = VpnTransportInfo(
+                /* type= */ 0,
+                /* sessionId= */ "sessionId",
+            )
+            val networkCapabilities = mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+                whenever(it.transportInfo).thenReturn(transportInfo)
+            }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
+
+            assertThat(underTest.isWifiDefault.value).isTrue()
+
+            job.cancel()
+        }
+
     @Test
     fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) {
         val job = underTest.isWifiDefault.launchIn(this)
@@ -260,6 +311,24 @@
     }
 
     @Test
+    fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.isWifiDefault.launchIn(this)
+
+            val capabilities = mock<NetworkCapabilities>().apply {
+                whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+            }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(underTest.isWifiDefault.value).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
     fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) {
         val job = underTest.isWifiDefault.launchIn(this)
 
@@ -467,6 +536,28 @@
         job.cancel()
     }
 
+    /** Regression test for b/266628069. */
+    @Test
+    fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
+        runBlocking(IMMEDIATE) {
+            var latest: WifiNetworkModel? = null
+            val job = underTest
+                .wifiNetwork
+                .onEach { latest = it }
+                .launchIn(this)
+
+            val transportInfo = VpnTransportInfo(
+                /* type= */ 0,
+                /* sessionId= */ "sessionId",
+            )
+            getNetworkCallback()
+                .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo))
+
+            assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+            job.cancel()
+        }
+
     @Test
     fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) {
         var latest: WifiNetworkModel? = null
@@ -535,6 +626,31 @@
     }
 
     @Test
+    fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
+        runBlocking(IMMEDIATE) {
+            var latest: WifiNetworkModel? = null
+            val job = underTest
+                .wifiNetwork
+                .onEach { latest = it }
+                .launchIn(this)
+
+            val capabilities = mock<NetworkCapabilities>().apply {
+                whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+            }
+
+            getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+            val latestActive = latest as WifiNetworkModel.Active
+            assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+            assertThat(latestActive.ssid).isEqualTo(SSID)
+
+            job.cancel()
+        }
+
+    @Test
     fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = runBlocking(IMMEDIATE) {
         var latest: WifiNetworkModel? = null
         val job = underTest
@@ -870,12 +986,12 @@
     }
 
     private fun createWifiNetworkCapabilities(
-        wifiInfo: WifiInfo,
+        transportInfo: TransportInfo,
         isValidated: Boolean = true,
     ): NetworkCapabilities {
         return mock<NetworkCapabilities>().also {
             whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
-            whenever(it.transportInfo).thenReturn(wifiInfo)
+            whenever(it.transportInfo).thenReturn(transportInfo)
             whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(isValidated)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
index 1cccd65c8..cc6be5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
@@ -92,6 +92,20 @@
     }
 
     @Test
+    fun onStylusAdded_internal_updatesNotificationSuppression() {
+        startable.onStylusAdded(STYLUS_DEVICE_ID)
+
+        verify(stylusUsiPowerUi, times(1)).updateSuppression(false)
+    }
+
+    @Test
+    fun onStylusAdded_external_noop() {
+        startable.onStylusAdded(EXTERNAL_DEVICE_ID)
+
+        verifyZeroInteractions(stylusUsiPowerUi)
+    }
+
+    @Test
     fun onStylusBluetoothConnected_refreshesNotification() {
         startable.onStylusBluetoothConnected(STYLUS_DEVICE_ID, "ANY")
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 99e2012..c7c6b94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -159,7 +159,7 @@
         underTest.displayView(getState())
         assertThat(fakeWakeLock.isHeld).isTrue()
 
-        underTest.removeView("id", "test reason")
+        underTest.removeView(DEFAULT_ID, "test reason")
 
         assertThat(fakeWakeLock.isHeld).isFalse()
     }
@@ -175,6 +175,8 @@
 
     @Test
     fun displayView_twiceWithDifferentIds_oldViewRemovedNewViewAdded() {
+        val listener = registerListener()
+
         underTest.displayView(
             ViewInfo(
                 name = "name",
@@ -199,10 +201,15 @@
         assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
         assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
         verify(windowManager).removeView(viewCaptor.allValues[0])
+        // Since the controller is still storing the older view in case it'll get re-displayed
+        // later, the listener shouldn't be notified
+        assertThat(listener.permanentlyRemovedIds).isEmpty()
     }
 
     @Test
     fun displayView_viewDoesNotDisappearsBeforeTimeout() {
+        val listener = registerListener()
+
         val state = getState()
         underTest.displayView(state)
         reset(windowManager)
@@ -210,10 +217,13 @@
         fakeClock.advanceTime(TIMEOUT_MS - 1)
 
         verify(windowManager, never()).removeView(any())
+        assertThat(listener.permanentlyRemovedIds).isEmpty()
     }
 
     @Test
     fun displayView_viewDisappearsAfterTimeout() {
+        val listener = registerListener()
+
         val state = getState()
         underTest.displayView(state)
         reset(windowManager)
@@ -221,10 +231,13 @@
         fakeClock.advanceTime(TIMEOUT_MS + 1)
 
         verify(windowManager).removeView(any())
+        assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
     }
 
     @Test
     fun displayView_calledAgainBeforeTimeout_timeoutReset() {
+        val listener = registerListener()
+
         // First, display the view
         val state = getState()
         underTest.displayView(state)
@@ -239,10 +252,13 @@
 
         // Verify we didn't hide the view
         verify(windowManager, never()).removeView(any())
+        assertThat(listener.permanentlyRemovedIds).isEmpty()
     }
 
     @Test
     fun displayView_calledAgainBeforeTimeout_eventuallyTimesOut() {
+        val listener = registerListener()
+
         // First, display the view
         val state = getState()
         underTest.displayView(state)
@@ -255,6 +271,7 @@
         fakeClock.advanceTime(TIMEOUT_MS + 1)
 
         verify(windowManager).removeView(any())
+        assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
     }
 
     @Test
@@ -271,25 +288,9 @@
     }
 
     @Test
-    fun viewUpdatedWithNewOnViewTimeoutRunnable_newRunnableUsed() {
-        var runnable1Run = false
-        underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
-            runnable1Run = true
-        }
-
-        var runnable2Run = false
-        underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
-            runnable2Run = true
-        }
-
-        fakeClock.advanceTime(TIMEOUT_MS + 1)
-
-        assertThat(runnable1Run).isFalse()
-        assertThat(runnable2Run).isTrue()
-    }
-
-    @Test
     fun multipleViewsWithDifferentIds_moreRecentReplacesOlder() {
+        val listener = registerListener()
+
         underTest.displayView(
             ViewInfo(
                 name = "name",
@@ -315,10 +316,16 @@
         assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
         verify(windowManager).removeView(viewCaptor.allValues[0])
         verify(configurationController, never()).removeCallback(any())
+
+        // Since the controller is still storing the older view in case it'll get re-displayed
+        // later, the listener shouldn't be notified
+        assertThat(listener.permanentlyRemovedIds).isEmpty()
     }
 
     @Test
-    fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
+    fun multipleViewsWithDifferentIds_newViewRemoved_previousViewIsDisplayed() {
+        val listener = registerListener()
+
         underTest.displayView(ViewInfo("First name", id = "id1"))
 
         verify(windowManager).addView(any(), any())
@@ -329,24 +336,35 @@
         verify(windowManager).removeView(any())
         verify(windowManager).addView(any(), any())
         reset(windowManager)
+        assertThat(listener.permanentlyRemovedIds).isEmpty()
 
+        // WHEN the current view is removed
         underTest.removeView("id2", "test reason")
 
+        // THEN it's correctly removed
         verify(windowManager).removeView(any())
+        assertThat(listener.permanentlyRemovedIds).containsExactly("id2")
+
+        // And the previous view is correctly added
         verify(windowManager).addView(any(), any())
         assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
         assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
 
+        // WHEN the previous view times out
         reset(windowManager)
         fakeClock.advanceTime(TIMEOUT_MS + 1)
 
+        // THEN it is also removed
         verify(windowManager).removeView(any())
         assertThat(underTest.activeViews.size).isEqualTo(0)
         verify(configurationController).removeCallback(any())
+        assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id2", "id1"))
     }
 
     @Test
     fun multipleViewsWithDifferentIds_oldViewRemoved_recentViewIsDisplayed() {
+        val listener = registerListener()
+
         underTest.displayView(ViewInfo("First name", id = "id1"))
 
         verify(windowManager).addView(any(), any())
@@ -361,7 +379,8 @@
         // WHEN an old view is removed
         underTest.removeView("id1", "test reason")
 
-        // THEN we don't update anything
+        // THEN we don't update anything except the listener
+        assertThat(listener.permanentlyRemovedIds).containsExactly("id1")
         verify(windowManager, never()).removeView(any())
         assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
         assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
@@ -372,10 +391,13 @@
         verify(windowManager).removeView(any())
         assertThat(underTest.activeViews.size).isEqualTo(0)
         verify(configurationController).removeCallback(any())
+        assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id1", "id2"))
     }
 
     @Test
     fun multipleViewsWithDifferentIds_threeDifferentViews_recentActiveViewIsDisplayed() {
+        val listener = registerListener()
+
         underTest.displayView(ViewInfo("First name", id = "id1"))
         underTest.displayView(ViewInfo("Second name", id = "id2"))
         underTest.displayView(ViewInfo("Third name", id = "id3"))
@@ -387,6 +409,7 @@
         underTest.removeView("id3", "test reason")
 
         verify(windowManager).removeView(any())
+        assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id3"))
         assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
         assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
         verify(configurationController, never()).removeCallback(any())
@@ -395,6 +418,7 @@
         underTest.removeView("id2", "test reason")
 
         verify(windowManager).removeView(any())
+        assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id3", "id2"))
         assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
         assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
         verify(configurationController, never()).removeCallback(any())
@@ -403,6 +427,7 @@
         fakeClock.advanceTime(TIMEOUT_MS + 1)
 
         verify(windowManager).removeView(any())
+        assertThat(listener.permanentlyRemovedIds).isEqualTo(listOf("id3", "id2", "id1"))
         assertThat(underTest.activeViews.size).isEqualTo(0)
         verify(configurationController).removeCallback(any())
     }
@@ -438,6 +463,8 @@
 
     @Test
     fun multipleViews_mostRecentViewRemoved_otherViewsTimedOutAndNotDisplayed() {
+        val listener = registerListener()
+
         underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
         fakeClock.advanceTime(1000)
         underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 4000))
@@ -451,10 +478,13 @@
         verify(windowManager, never()).addView(any(), any())
         assertThat(underTest.activeViews.size).isEqualTo(0)
         verify(configurationController).removeCallback(any())
+        assertThat(listener.permanentlyRemovedIds).containsExactly("id1", "id2", "id3")
     }
 
     @Test
     fun multipleViews_mostRecentViewRemoved_viewWithShortTimeLeftNotDisplayed() {
+        val listener = registerListener()
+
         underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
         fakeClock.advanceTime(1000)
         underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 2500))
@@ -467,10 +497,13 @@
         verify(windowManager, never()).addView(any(), any())
         assertThat(underTest.activeViews.size).isEqualTo(0)
         verify(configurationController).removeCallback(any())
+        assertThat(listener.permanentlyRemovedIds).containsExactly("id1", "id2")
     }
 
     @Test
     fun lowerThenHigherPriority_higherReplacesLower() {
+        val listener = registerListener()
+
         underTest.displayView(
             ViewInfo(
                 name = "normal",
@@ -499,10 +532,15 @@
         verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
         assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
         verify(configurationController, never()).removeCallback(any())
+        // Since the controller is still storing the older view in case it'll get re-displayed
+        // later, the listener shouldn't be notified
+        assertThat(listener.permanentlyRemovedIds).isEmpty()
     }
 
     @Test
     fun lowerThenHigherPriority_lowerPriorityRedisplayed() {
+        val listener = registerListener()
+
         underTest.displayView(
             ViewInfo(
                 name = "normal",
@@ -537,6 +575,7 @@
 
         // THEN the normal view is re-displayed
         verify(windowManager).removeView(viewCaptor.allValues[1])
+        assertThat(listener.permanentlyRemovedIds).containsExactly("critical")
         verify(windowManager).addView(any(), capture(windowParamsCaptor))
         assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
         verify(configurationController, never()).removeCallback(any())
@@ -544,6 +583,8 @@
 
     @Test
     fun lowerThenHigherPriority_lowerPriorityNotRedisplayedBecauseTimedOut() {
+        val listener = registerListener()
+
         underTest.displayView(
             ViewInfo(
                 name = "normal",
@@ -573,6 +614,7 @@
         verify(windowManager, never()).addView(any(), any())
         assertThat(underTest.activeViews).isEmpty()
         verify(configurationController).removeCallback(any())
+        assertThat(listener.permanentlyRemovedIds).containsExactly("critical", "normal")
     }
 
     @Test
@@ -609,6 +651,8 @@
 
     @Test
     fun higherThenLowerPriority_lowerEventuallyDisplayed() {
+        val listener = registerListener()
+
         underTest.displayView(
             ViewInfo(
                 name = "critical",
@@ -644,6 +688,7 @@
 
         // THEN the second normal view is displayed
         verify(windowManager).removeView(viewCaptor.value)
+        assertThat(listener.permanentlyRemovedIds).containsExactly("critical")
         verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
         assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
         assertThat(underTest.activeViews.size).isEqualTo(1)
@@ -652,6 +697,8 @@
 
     @Test
     fun higherThenLowerPriority_lowerNotDisplayedBecauseTimedOut() {
+        val listener = registerListener()
+
         underTest.displayView(
             ViewInfo(
                 name = "critical",
@@ -691,10 +738,13 @@
         verify(windowManager, never()).addView(any(), any())
         assertThat(underTest.activeViews).isEmpty()
         verify(configurationController).removeCallback(any())
+        assertThat(listener.permanentlyRemovedIds).containsExactly("critical", "normal")
     }
 
     @Test
     fun criticalThenNewCritical_newCriticalDisplayed() {
+        val listener = registerListener()
+
         underTest.displayView(
             ViewInfo(
                 name = "critical 1",
@@ -724,10 +774,15 @@
         assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 2")
         assertThat(underTest.activeViews.size).isEqualTo(2)
         verify(configurationController, never()).removeCallback(any())
+        // Since the controller is still storing the older view in case it'll get re-displayed
+        // later, the listener shouldn't be notified
+        assertThat(listener.permanentlyRemovedIds).isEmpty()
     }
 
     @Test
     fun normalThenNewNormal_newNormalDisplayed() {
+        val listener = registerListener()
+
         underTest.displayView(
             ViewInfo(
                 name = "normal 1",
@@ -757,6 +812,9 @@
         assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 2")
         assertThat(underTest.activeViews.size).isEqualTo(2)
         verify(configurationController, never()).removeCallback(any())
+        // Since the controller is still storing the older view in case it'll get re-displayed
+        // later, the listener shouldn't be notified
+        assertThat(listener.permanentlyRemovedIds).isEmpty()
     }
 
     @Test
@@ -957,25 +1015,103 @@
     }
 
     @Test
-    fun removeView_viewRemovedAndRemovalLogged() {
+    fun removeView_viewRemovedAndRemovalLoggedAndListenerNotified() {
+        val listener = registerListener()
+
         // First, add the view
         underTest.displayView(getState())
 
         // Then, remove it
         val reason = "test reason"
-        val deviceId = "id"
-        underTest.removeView(deviceId, reason)
+        underTest.removeView(DEFAULT_ID, reason)
 
         verify(windowManager).removeView(any())
-        verify(logger).logViewRemoval(deviceId, reason)
+        verify(logger).logViewRemoval(DEFAULT_ID, reason)
         verify(configurationController).removeCallback(any())
+        assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
     }
 
     @Test
-    fun removeView_noAdd_viewNotRemoved() {
+    fun removeView_noAdd_viewNotRemovedAndListenerNotNotified() {
+        val listener = registerListener()
+
         underTest.removeView("id", "reason")
 
         verify(windowManager, never()).removeView(any())
+        assertThat(listener.permanentlyRemovedIds).isEmpty()
+    }
+
+    @Test
+    fun listenerRegistered_notifiedOnRemoval() {
+        val listener = registerListener()
+        underTest.displayView(getState())
+
+        underTest.removeView(DEFAULT_ID, "reason")
+
+        assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+    }
+
+    @Test
+    fun listenerRegistered_notifiedOnTimedOutEvenWhenNotDisplayed() {
+        val listener = registerListener()
+        underTest.displayView(
+            ViewInfo(
+                id = "id1",
+                name = "name1",
+                timeoutMs = 3000,
+            ),
+        )
+
+        // Display a second view
+        underTest.displayView(
+            ViewInfo(
+                id = "id2",
+                name = "name2",
+                timeoutMs = 2500,
+            ),
+        )
+
+        // WHEN the second view times out
+        fakeClock.advanceTime(2501)
+
+        // THEN the listener is notified of both IDs, since id2 timed out and id1 doesn't have
+        // enough time left to be redisplayed
+        assertThat(listener.permanentlyRemovedIds).containsExactly("id1", "id2")
+    }
+
+    @Test
+    fun multipleListeners_allNotified() {
+        val listener1 = registerListener()
+        val listener2 = registerListener()
+        val listener3 = registerListener()
+
+        underTest.displayView(getState())
+
+        underTest.removeView(DEFAULT_ID, "reason")
+
+        assertThat(listener1.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+        assertThat(listener2.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+        assertThat(listener3.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+    }
+
+    @Test
+    fun sameListenerRegisteredMultipleTimes_onlyNotifiedOnce() {
+        val listener = registerListener()
+        underTest.registerListener(listener)
+        underTest.registerListener(listener)
+
+        underTest.displayView(getState())
+
+        underTest.removeView(DEFAULT_ID, "reason")
+
+        assertThat(listener.permanentlyRemovedIds).hasSize(1)
+        assertThat(listener.permanentlyRemovedIds).containsExactly(DEFAULT_ID)
+    }
+
+    private fun registerListener(): Listener {
+        return Listener().also {
+            underTest.registerListener(it)
+        }
     }
 
     private fun getState(name: String = "name") = ViewInfo(name)
@@ -1030,9 +1166,17 @@
         override val windowTitle: String = "Window Title",
         override val wakeReason: String = "WAKE_REASON",
         override val timeoutMs: Int = TIMEOUT_MS.toInt(),
-        override val id: String = "id",
+        override val id: String = DEFAULT_ID,
         override val priority: ViewPriority = ViewPriority.NORMAL,
     ) : TemporaryViewInfo()
+
+    inner class Listener : TemporaryViewDisplayController.Listener {
+        val permanentlyRemovedIds = mutableListOf<String>()
+        override fun onInfoPermanentlyRemoved(id: String, reason: String) {
+            permanentlyRemovedIds.add(id)
+        }
+    }
 }
 
 private const val TIMEOUT_MS = 10000L
+private const val DEFAULT_ID = "defaultId"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 90178c6..fc7436a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -20,6 +20,7 @@
 import android.os.VibrationEffect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
@@ -43,6 +44,8 @@
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
@@ -54,6 +57,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
@@ -62,7 +66,7 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class ChipbarCoordinatorTest : SysuiTestCase() {
-    private lateinit var underTest: FakeChipbarCoordinator
+    private lateinit var underTest: ChipbarCoordinator
 
     @Mock private lateinit var logger: ChipbarLogger
     @Mock private lateinit var accessibilityManager: AccessibilityManager
@@ -74,6 +78,8 @@
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var viewUtil: ViewUtil
     @Mock private lateinit var vibratorHelper: VibratorHelper
+    @Mock private lateinit var swipeGestureHandler: SwipeChipbarAwayGestureHandler
+    private lateinit var chipbarAnimator: TestChipbarAnimator
     private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
     private lateinit var fakeWakeLock: WakeLockFake
     private lateinit var fakeClock: FakeSystemClock
@@ -93,9 +99,10 @@
         fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
 
         uiEventLoggerFake = UiEventLoggerFake()
+        chipbarAnimator = TestChipbarAnimator()
 
         underTest =
-            FakeChipbarCoordinator(
+            ChipbarCoordinator(
                 context,
                 logger,
                 windowManager,
@@ -104,8 +111,10 @@
                 configurationController,
                 dumpManager,
                 powerManager,
+                chipbarAnimator,
                 falsingManager,
                 falsingCollector,
+                swipeGestureHandler,
                 viewUtil,
                 vibratorHelper,
                 fakeWakeLockBuilder,
@@ -365,6 +374,26 @@
         verify(vibratorHelper).vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK))
     }
 
+    /** Regression test for b/266119467. */
+    @Test
+    fun displayView_animationFailure_viewsStillBecomeVisible() {
+        chipbarAnimator.allowAnimation = false
+
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.id.check_box, null),
+                Text.Loaded("text"),
+                endItem = ChipbarEndItem.Loading,
+            )
+        )
+
+        val view = getChipbarView()
+        assertThat(view.getInnerView().alpha).isEqualTo(1f)
+        assertThat(view.getStartIconView().alpha).isEqualTo(1f)
+        assertThat(view.getLoadingIcon().alpha).isEqualTo(1f)
+        assertThat(view.getChipTextView().alpha).isEqualTo(1f)
+    }
+
     @Test
     fun updateView_viewUpdated() {
         // First, display a view
@@ -430,17 +459,137 @@
         verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("new title text"), any())
     }
 
+    /** Regression test for b/266209420. */
+    @Test
+    fun displayViewThenImmediateRemoval_viewStillRemoved() {
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Error,
+            ),
+        )
+        val chipbarView = getChipbarView()
+
+        underTest.removeView(DEVICE_ID, "test reason")
+
+        verify(windowManager).removeView(chipbarView)
+    }
+
+    /** Regression test for b/266209420. */
+    @Test
+    fun removeView_animationFailure_viewStillRemoved() {
+        chipbarAnimator.allowAnimation = false
+
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Error,
+            ),
+        )
+        val chipbarView = getChipbarView()
+
+        underTest.removeView(DEVICE_ID, "test reason")
+
+        verify(windowManager).removeView(chipbarView)
+    }
+
+    @Test
+    fun swipeToDismiss_false_neverListensForGesture() {
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Loading,
+                allowSwipeToDismiss = false,
+            )
+        )
+
+        verify(swipeGestureHandler, never()).addOnGestureDetectedCallback(any(), any())
+    }
+
+    @Test
+    fun swipeToDismiss_true_listensForGesture() {
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Loading,
+                allowSwipeToDismiss = true,
+            )
+        )
+
+        verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), any())
+    }
+
+    @Test
+    fun swipeToDismiss_swipeOccurs_viewDismissed() {
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Loading,
+                allowSwipeToDismiss = true,
+            )
+        )
+        val view = getChipbarView()
+
+        val callbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
+        verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), capture(callbackCaptor))
+
+        callbackCaptor.value.invoke(MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0))
+
+        verify(windowManager).removeView(view)
+    }
+
+    @Test
+    fun swipeToDismiss_viewUpdatedToFalse_swipeOccurs_viewNotDismissed() {
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Loading,
+                allowSwipeToDismiss = true,
+            )
+        )
+        val view = getChipbarView()
+        val callbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
+        verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), capture(callbackCaptor))
+
+        // WHEN the view is updated to not allow swipe-to-dismiss
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Loading,
+                allowSwipeToDismiss = false,
+            )
+        )
+
+        // THEN the callback is removed
+        verify(swipeGestureHandler).removeOnGestureDetectedCallback(any())
+
+        // And WHEN the old callback is invoked
+        callbackCaptor.value.invoke(MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0))
+
+        // THEN it is ignored and view isn't removed
+        verify(windowManager, never()).removeView(view)
+    }
+
     private fun createChipbarInfo(
         startIcon: Icon,
         text: Text,
         endItem: ChipbarEndItem?,
         vibrationEffect: VibrationEffect? = null,
+        allowSwipeToDismiss: Boolean = false,
     ): ChipbarInfo {
         return ChipbarInfo(
             TintedIcon(startIcon, tintAttr = null),
             text,
             endItem,
             vibrationEffect,
+            allowSwipeToDismiss,
             windowTitle = WINDOW_TITLE,
             wakeReason = WAKE_REASON,
             timeoutMs = TIMEOUT,
@@ -453,8 +602,9 @@
 
     private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
 
-    private fun ViewGroup.getChipText(): String =
-        (this.requireViewById<TextView>(R.id.text)).text as String
+    private fun ViewGroup.getChipTextView() = this.requireViewById<TextView>(R.id.text)
+
+    private fun ViewGroup.getChipText(): String = this.getChipTextView().text as String
 
     private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading)
 
@@ -467,6 +617,25 @@
         verify(windowManager).addView(viewCaptor.capture(), any())
         return viewCaptor.value as ViewGroup
     }
+
+    /** Test class that lets us disallow animations. */
+    inner class TestChipbarAnimator : ChipbarAnimator() {
+        var allowAnimation: Boolean = true
+
+        override fun animateViewIn(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+            if (!allowAnimation) {
+                return false
+            }
+            return super.animateViewIn(innerView, onAnimationEnd)
+        }
+
+        override fun animateViewOut(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean {
+            if (!allowAnimation) {
+                return false
+            }
+            return super.animateViewOut(innerView, onAnimationEnd)
+        }
+    }
 }
 
 private const val TIMEOUT = 10000
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
deleted file mode 100644
index 4ef4e6c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2022 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.systemui.temporarydisplay.chipbar
-
-import android.content.Context
-import android.os.PowerManager
-import android.view.ViewGroup
-import android.view.WindowManager
-import android.view.accessibility.AccessibilityManager
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.time.SystemClock
-import com.android.systemui.util.view.ViewUtil
-import com.android.systemui.util.wakelock.WakeLock
-
-/** A fake implementation of [ChipbarCoordinator] for testing. */
-class FakeChipbarCoordinator(
-    context: Context,
-    logger: ChipbarLogger,
-    windowManager: WindowManager,
-    mainExecutor: DelayableExecutor,
-    accessibilityManager: AccessibilityManager,
-    configurationController: ConfigurationController,
-    dumpManager: DumpManager,
-    powerManager: PowerManager,
-    falsingManager: FalsingManager,
-    falsingCollector: FalsingCollector,
-    viewUtil: ViewUtil,
-    vibratorHelper: VibratorHelper,
-    wakeLockBuilder: WakeLock.Builder,
-    systemClock: SystemClock,
-) :
-    ChipbarCoordinator(
-        context,
-        logger,
-        windowManager,
-        mainExecutor,
-        accessibilityManager,
-        configurationController,
-        dumpManager,
-        powerManager,
-        falsingManager,
-        falsingCollector,
-        viewUtil,
-        vibratorHelper,
-        wakeLockBuilder,
-        systemClock,
-    ) {
-    override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
-        // Just bypass the animation in tests
-        onAnimationEnd.run()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt
new file mode 100644
index 0000000..c539246
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.systemui.temporarydisplay.chipbar
+
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class SwipeChipbarAwayGestureHandlerTest : SysuiTestCase() {
+
+    private lateinit var underTest: SwipeChipbarAwayGestureHandler
+
+    @Before
+    fun setUp() {
+        underTest = SwipeChipbarAwayGestureHandler(context, FakeDisplayTracker(mContext), mock())
+    }
+
+    @Test
+    fun startOfGestureIsWithinBounds_noViewFetcher_returnsFalse() {
+        assertThat(underTest.startOfGestureIsWithinBounds(createMotionEvent())).isFalse()
+    }
+
+    @Test
+    fun startOfGestureIsWithinBounds_usesViewFetcher_aboveBottom_returnsTrue() {
+        val view = createMockView()
+
+        underTest.setViewFetcher { view }
+
+        val motionEvent = createMotionEvent(y = VIEW_BOTTOM - 100f)
+        assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isTrue()
+    }
+
+    @Test
+    fun startOfGestureIsWithinBounds_usesViewFetcher_slightlyBelowBottom_returnsTrue() {
+        val view = createMockView()
+
+        underTest.setViewFetcher { view }
+
+        val motionEvent = createMotionEvent(y = VIEW_BOTTOM + 20f)
+        assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isTrue()
+    }
+
+    @Test
+    fun startOfGestureIsWithinBounds_usesViewFetcher_tooFarDown_returnsFalse() {
+        val view = createMockView()
+
+        underTest.setViewFetcher { view }
+
+        val motionEvent = createMotionEvent(y = VIEW_BOTTOM * 4f)
+        assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isFalse()
+    }
+
+    @Test
+    fun startOfGestureIsWithinBounds_viewFetcherReset_returnsFalse() {
+        val view = createMockView()
+
+        underTest.setViewFetcher { view }
+
+        val motionEvent = createMotionEvent(y = VIEW_BOTTOM - 100f)
+        assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isTrue()
+
+        underTest.resetViewFetcher()
+        assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isFalse()
+    }
+
+    private fun createMotionEvent(y: Float = 0f): MotionEvent {
+        return MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, y, 0)
+    }
+
+    private fun createMockView(): View {
+        return mock<View>().also {
+            doAnswer { invocation ->
+                    val out: Rect = invocation.getArgument(0)
+                    out.set(0, 0, 0, VIEW_BOTTOM)
+                    null
+                }
+                .whenever(it)
+                .getBoundsOnScreen(any())
+        }
+    }
+
+    private companion object {
+        const val VIEW_BOTTOM = 455
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 2a93fff..1710709 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -172,7 +172,7 @@
         verify(mDumpManager).registerDumpable(any(), any());
         verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
         verify(mSecureSettings).registerContentObserverForUser(
-                eq(Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)),
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
                 eq(false), mSettingsObserver.capture(), eq(UserHandle.USER_ALL)
         );
     }
@@ -790,15 +790,15 @@
 
         reset(mResources);
         when(mResources.getColor(eq(android.R.color.system_accent1_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().getS500());
         when(mResources.getColor(eq(android.R.color.system_accent2_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().getS500());
         when(mResources.getColor(eq(android.R.color.system_accent3_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().getS500());
         when(mResources.getColor(eq(android.R.color.system_neutral1_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().getS500());
         when(mResources.getColor(eq(android.R.color.system_neutral2_500), any()))
-                .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().get(6));
+                .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().getS500());
 
         // Defers event because we already have initial colors.
         verify(mThemeOverlayApplier, never())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 30c4f97..f4226bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -175,8 +175,10 @@
 
             fold()
             underTest.onScreenTurningOn({})
-            underTest.onStartedWakingUp()
+            // The body of onScreenTurningOn is executed on fakeExecutor,
+            // run all pending tasks before calling the next method
             fakeExecutor.runAllReady()
+            underTest.onStartedWakingUp()
 
             verify(latencyTracker).onActionStart(any())
             verify(latencyTracker).onActionCancel(any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
index c316402..4a28cd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
@@ -26,6 +26,10 @@
         listeners.forEach { it.onTransitionFinished() }
     }
 
+    override fun onTransitionFinishing() {
+        listeners.forEach { it.onTransitionFinishing() }
+    }
+
     override fun onTransitionProgress(progress: Float) {
         listeners.forEach { it.onTransitionProgress(progress) }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
new file mode 100644
index 0000000..d3fdbd9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.systemui.unfold
+
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UnfoldHapticsPlayerTest : SysuiTestCase() {
+
+    private val progressProvider = TestUnfoldTransitionProvider()
+    private val vibrator: Vibrator = mock()
+    private val testFoldProvider = TestFoldProvider()
+
+    private lateinit var player: UnfoldHapticsPlayer
+
+    @Before
+    fun before() {
+        player = UnfoldHapticsPlayer(progressProvider, testFoldProvider, Runnable::run, vibrator)
+    }
+
+    @Test
+    fun testUnfoldingTransitionFinishingEarly_playsHaptics() {
+        testFoldProvider.onFoldUpdate(isFolded = true)
+        testFoldProvider.onFoldUpdate(isFolded = false)
+        progressProvider.onTransitionStarted()
+        progressProvider.onTransitionProgress(0.5f)
+        progressProvider.onTransitionFinishing()
+
+        verify(vibrator).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun testUnfoldingTransitionFinishingLate_doesNotPlayHaptics() {
+        testFoldProvider.onFoldUpdate(isFolded = true)
+        testFoldProvider.onFoldUpdate(isFolded = false)
+        progressProvider.onTransitionStarted()
+        progressProvider.onTransitionProgress(0.99f)
+        progressProvider.onTransitionFinishing()
+
+        verify(vibrator, never()).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun testFoldingAfterUnfolding_doesNotPlayHaptics() {
+        // Unfold
+        testFoldProvider.onFoldUpdate(isFolded = true)
+        testFoldProvider.onFoldUpdate(isFolded = false)
+        progressProvider.onTransitionStarted()
+        progressProvider.onTransitionProgress(0.5f)
+        progressProvider.onTransitionFinishing()
+        progressProvider.onTransitionFinished()
+        clearInvocations(vibrator)
+
+        // Fold
+        progressProvider.onTransitionStarted()
+        progressProvider.onTransitionProgress(0.5f)
+        progressProvider.onTransitionFinished()
+        testFoldProvider.onFoldUpdate(isFolded = true)
+
+        verify(vibrator, never()).vibrate(any<VibrationEffect>())
+    }
+
+    @Test
+    fun testUnfoldingAfterFoldingAndUnfolding_playsHaptics() {
+        // Unfold
+        testFoldProvider.onFoldUpdate(isFolded = true)
+        testFoldProvider.onFoldUpdate(isFolded = false)
+        progressProvider.onTransitionStarted()
+        progressProvider.onTransitionProgress(0.5f)
+        progressProvider.onTransitionFinishing()
+        progressProvider.onTransitionFinished()
+
+        // Fold
+        progressProvider.onTransitionStarted()
+        progressProvider.onTransitionProgress(0.5f)
+        progressProvider.onTransitionFinished()
+        testFoldProvider.onFoldUpdate(isFolded = true)
+        clearInvocations(vibrator)
+
+        // Unfold again
+        testFoldProvider.onFoldUpdate(isFolded = true)
+        testFoldProvider.onFoldUpdate(isFolded = false)
+        progressProvider.onTransitionStarted()
+        progressProvider.onTransitionProgress(0.5f)
+        progressProvider.onTransitionFinishing()
+        progressProvider.onTransitionFinished()
+
+        verify(vibrator).vibrate(any<VibrationEffect>())
+    }
+
+    private class TestFoldProvider : FoldProvider {
+        private val listeners = arrayListOf<FoldProvider.FoldCallback>()
+
+        override fun registerCallback(callback: FoldProvider.FoldCallback, executor: Executor) {
+            listeners += callback
+        }
+
+        override fun unregisterCallback(callback: FoldProvider.FoldCallback) {
+            listeners -= callback
+        }
+
+        fun onFoldUpdate(isFolded: Boolean) {
+            listeners.forEach { it.onFoldUpdated(isFolded) }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 034c618..ccf378a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,12 +17,17 @@
 
 package com.android.systemui.user.data.repository
 
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
 import android.content.pm.UserInfo
+import android.os.IRemoteCallback
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.FakeSettings
@@ -39,7 +44,14 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -48,6 +60,8 @@
 class UserRepositoryImplTest : SysuiTestCase() {
 
     @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var activityManager: IActivityManager
+    @Captor private lateinit var userSwitchObserver: ArgumentCaptor<UserSwitchObserver>
 
     private lateinit var underTest: UserRepositoryImpl
 
@@ -214,6 +228,34 @@
         assertThat(selectedUserInfo?.id).isEqualTo(1)
     }
 
+    @Test
+    fun userSwitchingInProgress_registersOnlyOneUserSwitchObserver() = runSelfCancelingTest {
+        underTest = create(this)
+
+        underTest.userSwitchingInProgress.launchIn(this)
+        underTest.userSwitchingInProgress.launchIn(this)
+        underTest.userSwitchingInProgress.launchIn(this)
+
+        verify(activityManager, times(1)).registerUserSwitchObserver(any(), anyString())
+    }
+
+    @Test
+    fun userSwitchingInProgress_propagatesStateFromActivityManager() = runSelfCancelingTest {
+        underTest = create(this)
+        verify(activityManager)
+            .registerUserSwitchObserver(userSwitchObserver.capture(), anyString())
+
+        userSwitchObserver.value.onUserSwitching(0, mock(IRemoteCallback::class.java))
+
+        var mostRecentSwitchingValue = false
+        underTest.userSwitchingInProgress.onEach { mostRecentSwitchingValue = it }.launchIn(this)
+
+        assertThat(mostRecentSwitchingValue).isTrue()
+
+        userSwitchObserver.value.onUserSwitchComplete(0)
+        assertThat(mostRecentSwitchingValue).isFalse()
+    }
+
     private fun createUserInfo(
         id: Int,
         isGuest: Boolean,
@@ -280,6 +322,8 @@
         }
 
     private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(FACE_AUTH_REFACTOR, true)
         return UserRepositoryImpl(
             appContext = context,
             manager = manager,
@@ -288,6 +332,8 @@
             backgroundDispatcher = IMMEDIATE,
             globalSettings = globalSettings,
             tracker = tracker,
+            activityManager = activityManager,
+            featureFlags = featureFlags,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
index 6bfc2f1..7d0d57b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
@@ -19,9 +19,12 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -35,6 +38,10 @@
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.yield
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -231,6 +238,170 @@
     }
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ThrottleFlowTest : SysuiTestCase() {
+
+    @Test
+    fun doesNotAffectEmissions_whenDelayAtLeastEqualToPeriod() = runTest {
+        // Arrange
+        val choreographer = createChoreographer(this)
+        val output = mutableListOf<Int>()
+        val collectJob = backgroundScope.launch {
+            flow {
+                emit(1)
+                delay(1000)
+                emit(2)
+            }.throttle(1000, choreographer.fakeClock).toList(output)
+        }
+
+        // Act
+        choreographer.advanceAndRun(0)
+
+        // Assert
+        assertThat(output).containsExactly(1)
+
+        // Act
+        choreographer.advanceAndRun(999)
+
+        // Assert
+        assertThat(output).containsExactly(1)
+
+        // Act
+        choreographer.advanceAndRun(1)
+
+        // Assert
+        assertThat(output).containsExactly(1, 2)
+
+        // Cleanup
+        collectJob.cancel()
+    }
+
+    @Test
+    fun delaysEmissions_withShorterThanPeriodDelay_untilPeriodElapses() = runTest {
+        // Arrange
+        val choreographer = createChoreographer(this)
+        val output = mutableListOf<Int>()
+        val collectJob = backgroundScope.launch {
+            flow {
+                emit(1)
+                delay(500)
+                emit(2)
+            }.throttle(1000, choreographer.fakeClock).toList(output)
+        }
+
+        // Act
+        choreographer.advanceAndRun(0)
+
+        // Assert
+        assertThat(output).containsExactly(1)
+
+        // Act
+        choreographer.advanceAndRun(500)
+        choreographer.advanceAndRun(499)
+
+        // Assert
+        assertThat(output).containsExactly(1)
+
+        // Act
+        choreographer.advanceAndRun(1)
+
+        // Assert
+        assertThat(output).containsExactly(1, 2)
+
+        // Cleanup
+        collectJob.cancel()
+    }
+
+    @Test
+    fun filtersAllButLastEmission_whenMultipleEmissionsInPeriod() = runTest {
+        // Arrange
+        val choreographer = createChoreographer(this)
+        val output = mutableListOf<Int>()
+        val collectJob = backgroundScope.launch {
+            flow {
+                emit(1)
+                delay(500)
+                emit(2)
+                delay(500)
+                emit(3)
+            }.throttle(1000, choreographer.fakeClock).toList(output)
+        }
+
+        // Act
+        choreographer.advanceAndRun(0)
+
+        // Assert
+        assertThat(output).containsExactly(1)
+
+        // Act
+        choreographer.advanceAndRun(500)
+        choreographer.advanceAndRun(499)
+
+        // Assert
+        assertThat(output).containsExactly(1)
+
+        // Act
+        choreographer.advanceAndRun(1)
+
+        // Assert
+        assertThat(output).containsExactly(1, 3)
+
+        // Cleanup
+        collectJob.cancel()
+    }
+
+    @Test
+    fun filtersAllButLastEmission_andDelaysIt_whenMultipleEmissionsInShorterThanPeriod() = runTest {
+        // Arrange
+        val choreographer = createChoreographer(this)
+        val output = mutableListOf<Int>()
+        val collectJob = backgroundScope.launch {
+            flow {
+                emit(1)
+                delay(500)
+                emit(2)
+                delay(250)
+                emit(3)
+            }.throttle(1000, choreographer.fakeClock).toList(output)
+        }
+
+        // Act
+        choreographer.advanceAndRun(0)
+
+        // Assert
+        assertThat(output).containsExactly(1)
+
+        // Act
+        choreographer.advanceAndRun(500)
+        choreographer.advanceAndRun(250)
+        choreographer.advanceAndRun(249)
+
+        // Assert
+        assertThat(output).containsExactly(1)
+
+        // Act
+        choreographer.advanceAndRun(1)
+
+        // Assert
+        assertThat(output).containsExactly(1, 3)
+
+        // Cleanup
+        collectJob.cancel()
+    }
+
+    private fun createChoreographer(testScope: TestScope) = object {
+        val fakeClock = FakeSystemClock()
+
+        fun advanceAndRun(millis: Long) {
+            fakeClock.advanceTime(millis)
+            testScope.advanceTimeBy(millis)
+            testScope.runCurrent()
+        }
+    }
+}
+
 private fun <T> assertThatFlow(flow: Flow<T>) =
     object {
         suspend fun emitsExactly(vararg emissions: T) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
index d0420f7..729168a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/leak/LeakReporterTest.java
@@ -22,13 +22,17 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.NotificationManager;
+import android.os.UserHandle;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 
 import org.junit.After;
 import org.junit.Before;
@@ -48,6 +52,7 @@
     private File mLeakDir;
     private File mLeakDump;
     private File mLeakHprof;
+    private UserTracker mUserTracker;
     private NotificationManager mNotificationManager;
 
     @Before
@@ -56,6 +61,9 @@
         mLeakDump = new File(mLeakDir, LeakReporter.LEAK_DUMP);
         mLeakHprof = new File(mLeakDir, LeakReporter.LEAK_HPROF);
 
+        mUserTracker = mock(UserTracker.class);
+        when(mUserTracker.getUserHandle()).thenReturn(
+                UserHandle.of(ActivityManager.getCurrentUser()));
         mNotificationManager = mock(NotificationManager.class);
         mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
 
@@ -65,7 +73,7 @@
             return null;
         }).when(mLeakDetector).dump(any(), any());
 
-        mLeakReporter = new LeakReporter(mContext, mLeakDetector, "test@example.com");
+        mLeakReporter = new LeakReporter(mContext, mUserTracker, mLeakDetector, "test@example.com");
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 915ea1a..0663004 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -48,6 +48,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
@@ -101,6 +102,8 @@
     @Mock
     private ActivityManager mActivityManager;
     @Mock
+    private UserTracker mUserTracker;
+    @Mock
     private DumpManager mDumpManager;
 
 
@@ -113,6 +116,7 @@
         // Initial non-set value
         when(mRingerModeLiveData.getValue()).thenReturn(-1);
         when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
         // Enable group volume adjustments
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
@@ -124,7 +128,7 @@
                 mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
                 mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
                 mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
-                mActivityManager, mDumpManager, mCallback);
+                mActivityManager, mUserTracker, mDumpManager, mCallback);
         mVolumeController.setEnableDialogs(true, true);
     }
 
@@ -233,12 +237,13 @@
                 CaptioningManager captioningManager,
                 KeyguardManager keyguardManager,
                 ActivityManager activityManager,
+                UserTracker userTracker,
                 DumpManager dumpManager,
                 C callback) {
             super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
                     notificationManager, optionalVibrator, iAudioService, accessibilityManager,
                     packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
-                    activityManager, dumpManager);
+                    activityManager, userTracker, dumpManager);
             mCallbacks = callback;
 
             ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
new file mode 100644
index 0000000..b527861
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
@@ -0,0 +1,243 @@
+/*
+ * 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.systemui.wallet.controller
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.ArrayList
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class WalletContextualSuggestionsControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var walletController: QuickAccessWalletController
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var mockContext: Context
+    @Captor private lateinit var broadcastReceiver: ArgumentCaptor<BroadcastReceiver>
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(
+                broadcastDispatcher.broadcastFlow<List<String>?>(
+                    any(),
+                    isNull(),
+                    any(),
+                    any(),
+                    any()
+                )
+            )
+            .thenCallRealMethod()
+
+        whenever(featureFlags.isEnabled(eq(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)))
+            .thenReturn(true)
+
+        whenever(CARD_1.cardId).thenReturn(ID_1)
+        whenever(CARD_2.cardId).thenReturn(ID_2)
+        whenever(CARD_3.cardId).thenReturn(ID_3)
+    }
+
+    @Test
+    fun `state - has wallet cards - received contextual cards`() = runTest {
+        setUpWalletClient(listOf(CARD_1, CARD_2))
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(
+            mockContext,
+            createContextualCardsIntent(listOf(ID_1, ID_2))
+        )
+
+        assertThat(latest()).containsExactly(CARD_1, CARD_2)
+    }
+
+    @Test
+    fun `state - no wallet cards - received contextual cards`() = runTest {
+        setUpWalletClient(emptyList())
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(
+            mockContext,
+            createContextualCardsIntent(listOf(ID_1, ID_2))
+        )
+
+        assertThat(latest()).isEmpty()
+    }
+
+    @Test
+    fun `state - has wallet cards - no contextual cards`() = runTest {
+        setUpWalletClient(listOf(CARD_1, CARD_2))
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(mockContext, createContextualCardsIntent(emptyList()))
+
+        assertThat(latest()).isEmpty()
+    }
+
+    @Test
+    fun `state - wallet cards error`() = runTest {
+        setUpWalletClient(shouldFail = true)
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(
+            mockContext,
+            createContextualCardsIntent(listOf(ID_1, ID_2))
+        )
+
+        assertThat(latest()).isEmpty()
+    }
+
+    @Test
+    fun `state - no contextual cards extra`() = runTest {
+        setUpWalletClient(listOf(CARD_1, CARD_2))
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verifyRegistered()
+        broadcastReceiver.value.onReceive(mockContext, Intent(INTENT_NAME))
+
+        assertThat(latest()).isEmpty()
+    }
+
+    @Test
+    fun `state - has wallet cards - received contextual cards - feature disabled`() = runTest {
+        whenever(featureFlags.isEnabled(eq(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)))
+            .thenReturn(false)
+        setUpWalletClient(listOf(CARD_1, CARD_2))
+        val latest =
+            collectLastValue(
+                createWalletContextualSuggestionsController(backgroundScope)
+                    .contextualSuggestionCards,
+            )
+
+        runCurrent()
+        verify(broadcastDispatcher, never()).broadcastFlow(any(), isNull(), any(), any())
+        assertThat(latest()).isNull()
+    }
+
+    private fun createWalletContextualSuggestionsController(
+        scope: CoroutineScope
+    ): WalletContextualSuggestionsController {
+        return WalletContextualSuggestionsController(
+            scope,
+            walletController,
+            broadcastDispatcher,
+            featureFlags
+        )
+    }
+
+    private fun verifyRegistered() {
+        verify(broadcastDispatcher)
+            .registerReceiver(capture(broadcastReceiver), any(), isNull(), isNull(), any(), any())
+    }
+
+    private fun createContextualCardsIntent(
+        ids: List<String> = emptyList(),
+    ): Intent {
+        val intent = Intent(INTENT_NAME)
+        intent.putStringArrayListExtra("cardIds", ArrayList(ids))
+        return intent
+    }
+
+    private fun setUpWalletClient(
+        cards: List<WalletCard> = emptyList(),
+        shouldFail: Boolean = false
+    ) {
+        whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
+            with(
+                invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
+            ) {
+                if (shouldFail) {
+                    onWalletCardRetrievalError(mock())
+                } else {
+                    onWalletCardsRetrieved(GetWalletCardsResponse(cards, 0))
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val ID_1: String = "123"
+        private val CARD_1: WalletCard = mock()
+        private const val ID_2: String = "456"
+        private val CARD_2: WalletCard = mock()
+        private const val ID_3: String = "789"
+        private val CARD_3: WalletCard = mock()
+        private val INTENT_NAME: String = "WalletSuggestionsIntent"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 0fdcb95..31cce4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -32,13 +32,13 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.hamcrest.MockitoHamcrest.intThat;
 
+import android.app.ActivityManager;
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Surface;
@@ -49,6 +49,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -81,6 +82,8 @@
     private Surface mSurface;
     @Mock
     private Context mMockContext;
+    @Mock
+    private UserTracker mUserTracker;
 
     @Mock
     private Bitmap mWallpaperBitmap;
@@ -108,13 +111,16 @@
         when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
 
         // set up wallpaper manager
-        when(mWallpaperManager.getBitmapAsUser(eq(UserHandle.USER_CURRENT), anyBoolean()))
+        when(mWallpaperManager.getBitmapAsUser(eq(ActivityManager.getCurrentUser()), anyBoolean()))
                 .thenReturn(mWallpaperBitmap);
         when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
 
         // set up surface
         when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
         doNothing().when(mSurface).hwuiDestroy();
+
+        // set up UserTracker
+        when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
     }
 
     @Test
@@ -170,7 +176,7 @@
     }
 
     private ImageWallpaper createImageWallpaper() {
-        return new ImageWallpaper(mFakeBackgroundExecutor) {
+        return new ImageWallpaper(mFakeBackgroundExecutor, mUserTracker) {
             @Override
             public Engine onCreateEngine() {
                 return new CanvasEngine() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index dec8080..bc33439 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -91,6 +91,8 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
@@ -286,6 +288,8 @@
 
     private TestableLooper mTestableLooper;
 
+    private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -332,7 +336,7 @@
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
-        mSysUiState = new SysUiState();
+        mSysUiState = new SysUiState(mDisplayTracker);
         mSysUiState.addCallback(sysUiFlags -> {
             mSysUiStateBubblesManageMenuExpanded =
                     (sysUiFlags
@@ -361,7 +365,8 @@
                         mock(Handler.class),
                         mock(NotifPipelineFlags.class),
                         mock(KeyguardNotificationVisibilityProvider.class),
-                        mock(UiEventLogger.class)
+                        mock(UiEventLogger.class),
+                        mock(UserTracker.class)
                 );
         when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
         mBubbleController = new TestableBubbleController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index e5316bc8..ceee0bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -24,6 +24,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -48,7 +49,8 @@
             Handler mainHandler,
             NotifPipelineFlags flags,
             KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            UserTracker userTracker) {
         super(contentResolver,
                 powerManager,
                 dreamManager,
@@ -61,7 +63,8 @@
                 mainHandler,
                 flags,
                 keyguardNotificationVisibilityProvider,
-                uiEventLogger);
+                uiEventLogger,
+                userTracker);
         mUseHeadsUp = true;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 7ae47b4..45489d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.notetask.NoteTaskInitializer;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -63,6 +64,7 @@
 @RunWith(AndroidJUnit4.class)
 public class WMShellTest extends SysuiTestCase {
     WMShell mWMShell;
+    FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
     @Mock ShellInterface mShellInterface;
     @Mock CommandQueue mCommandQueue;
@@ -100,6 +102,7 @@
                 mProtoTracer,
                 mWakefulnessLifecycle,
                 mUserTracker,
+                mDisplayTracker,
                 mNoteTaskInitializer,
                 mSysUiMainExecutor
         );
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index bf2235a..1bab997 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -132,8 +132,10 @@
 
     @After
     public void SysuiTeardown() {
-        InstrumentationRegistry.registerInstance(mRealInstrumentation,
-                InstrumentationRegistry.getArguments());
+        if (mRealInstrumentation != null) {
+            InstrumentationRegistry.registerInstance(mRealInstrumentation,
+                    InstrumentationRegistry.getArguments());
+        }
         if (TestableLooper.get(this) != null) {
             TestableLooper.get(this).processAllMessages();
             // Must remove static reference to this test object to prevent leak (b/261039202)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
index 990db77..f723a9e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
@@ -23,14 +23,20 @@
     isUnlocked: Boolean = true,
     isShowingAlternateAuthOnUnlock: Boolean = false,
     interactionJankMonitor: InteractionJankMonitor = mock(InteractionJankMonitor::class.java),
+    isPredictiveBackQsDialogAnim: Boolean = false,
 ): DialogLaunchAnimator {
     return DialogLaunchAnimator(
-        FakeCallback(
-            isUnlocked = isUnlocked,
-            isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock,
-        ),
-        interactionJankMonitor,
-        fakeLaunchAnimator(),
+        callback =
+            FakeCallback(
+                isUnlocked = isUnlocked,
+                isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock,
+            ),
+        interactionJankMonitor = interactionJankMonitor,
+        featureFlags =
+            object : AnimationFeatureFlags {
+                override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
+            },
+        launchAnimator = fakeLaunchAnimator(),
         isForTesting = true,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 6c82cef..b94f816e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -38,12 +38,6 @@
         }
     }
 
-    fun set(flag: DeviceConfigBooleanFlag, value: Boolean) {
-        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
-            notifyFlagChanged(flag)
-        }
-    }
-
     fun set(flag: ResourceBooleanFlag, value: Boolean) {
         if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
             notifyFlagChanged(flag)
@@ -73,7 +67,7 @@
             listeners.forEach { listener ->
                 listener.onFlagChanged(
                     object : FlagListenable.FlagEvent {
-                        override val flagId = flag.id
+                        override val flagName = flag.name
                         override fun requestNoRestart() {}
                     }
                 )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
similarity index 95%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index f3e52de..044679d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -21,7 +21,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-class FakeBiometricRepository : BiometricRepository {
+class FakeBiometricSettingsRepository : BiometricSettingsRepository {
 
     private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
     override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
new file mode 100644
index 0000000..5641832
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.systemui.keyguard.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
+    private val _isLockedOut = MutableStateFlow<Boolean>(false)
+    override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
+
+    fun setLockedOut(lockedOut: Boolean) {
+        _isLockedOut.value = lockedOut
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
new file mode 100644
index 0000000..d0383e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 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.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
+import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
+import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [KeyguardRepository] */
+class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
+    private val _primaryBouncerVisible = MutableStateFlow(false)
+    override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
+    private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
+    override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
+    private val _primaryBouncerShowingSoon = MutableStateFlow(false)
+    override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
+    private val _primaryBouncerHide = MutableStateFlow(false)
+    override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
+    private val _primaryBouncerStartingToHide = MutableStateFlow(false)
+    override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
+    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+    override val primaryBouncerStartingDisappearAnimation =
+        _primaryBouncerDisappearAnimation.asStateFlow()
+    private val _primaryBouncerScrimmed = MutableStateFlow(false)
+    override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+    private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
+    override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
+    private val _keyguardPosition = MutableStateFlow(0f)
+    override val keyguardPosition = _keyguardPosition.asStateFlow()
+    private val _onScreenTurnedOff = MutableStateFlow(false)
+    override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
+    private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
+    override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
+    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
+    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
+    override val showMessage = _showMessage.asStateFlow()
+    private val _resourceUpdateRequests = MutableStateFlow(false)
+    override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+    override val bouncerPromptReason = 0
+    override val bouncerErrorMessage: CharSequence? = null
+    private val _isAlternateBouncerVisible = MutableStateFlow(false)
+    override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
+    override var lastAlternateBouncerVisibleTime: Long = 0L
+    private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
+    override val isAlternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
+
+    override fun setPrimaryScrimmed(isScrimmed: Boolean) {
+        _primaryBouncerScrimmed.value = isScrimmed
+    }
+
+    override fun setPrimaryVisible(isVisible: Boolean) {
+        _primaryBouncerVisible.value = isVisible
+    }
+
+    override fun setAlternateVisible(isVisible: Boolean) {
+        _isAlternateBouncerVisible.value = isVisible
+    }
+
+    override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
+        _isAlternateBouncerUIAvailable.value = isAvailable
+    }
+
+    override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
+        _primaryBouncerShow.value = keyguardBouncerModel
+    }
+
+    override fun setPrimaryShowingSoon(showingSoon: Boolean) {
+        _primaryBouncerShowingSoon.value = showingSoon
+    }
+
+    override fun setPrimaryHide(hide: Boolean) {
+        _primaryBouncerHide.value = hide
+    }
+
+    override fun setPrimaryStartingToHide(startingToHide: Boolean) {
+        _primaryBouncerStartingToHide.value = startingToHide
+    }
+
+    override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
+        _primaryBouncerDisappearAnimation.value = runnable
+    }
+
+    override fun setPanelExpansion(panelExpansion: Float) {
+        _panelExpansionAmount.value = panelExpansion
+    }
+
+    override fun setKeyguardPosition(keyguardPosition: Float) {
+        _keyguardPosition.value = keyguardPosition
+    }
+
+    override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
+        _resourceUpdateRequests.value = willUpdateResources
+    }
+
+    override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
+        _showMessage.value = bouncerShowMessageModel
+    }
+
+    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+        _keyguardAuthenticated.value = keyguardAuthenticated
+    }
+
+    override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
+        _isBackButtonEnabled.value = isBackButtonEnabled
+    }
+
+    override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
+        _onScreenTurnedOff.value = onScreenTurnedOff
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 15b4736..065fe89 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -29,6 +29,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [KeyguardRepository] */
 class FakeKeyguardRepository : KeyguardRepository {
@@ -101,6 +102,13 @@
     private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null)
     override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource
 
+    private val _isQuickSettingsVisible = MutableStateFlow(false)
+    override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow()
+
+    override fun setQuickSettingsVisible(isVisible: Boolean) {
+        _isQuickSettingsVisible.value = isVisible
+    }
+
     override fun isKeyguardShowing(): Boolean {
         return _isKeyguardShowing.value
     }
@@ -169,6 +177,10 @@
         _dozeTransitionModel.value = model
     }
 
+    fun setStatusBarState(state: StatusBarState) {
+        _statusBarState.value = state
+    }
+
     override fun isUdfpsSupported(): Boolean {
         return _isUdfpsSupported.value
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 6c44244..eac1bd1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -22,13 +22,15 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import java.util.UUID
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
 
 /** Fake implementation of [KeyguardTransitionRepository] */
 class FakeKeyguardTransitionRepository : KeyguardTransitionRepository {
 
-    private val _transitions = MutableSharedFlow<TransitionStep>()
+    private val _transitions =
+        MutableSharedFlow<TransitionStep>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
     override val transitions: SharedFlow<TransitionStep> = _transitions
 
     suspend fun sendTransitionStep(step: TransitionStep) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
new file mode 100644
index 0000000..1403cea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.systemui.settings
+
+import android.content.Context
+import android.hardware.display.DisplayManager
+import android.view.Display
+import java.util.concurrent.Executor
+
+class FakeDisplayTracker constructor(val context: Context) : DisplayTracker {
+    val displayManager: DisplayManager = context.getSystemService(DisplayManager::class.java)!!
+    override var defaultDisplayId: Int = Display.DEFAULT_DISPLAY
+    override var allDisplays: Array<Display> = displayManager.displays
+
+    private val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
+    private val brightnessCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
+    override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) {
+        displayCallbacks.add(callback)
+    }
+    override fun addBrightnessChangeCallback(
+        callback: DisplayTracker.Callback,
+        executor: Executor
+    ) {
+        brightnessCallbacks.add(callback)
+    }
+
+    override fun removeCallback(callback: DisplayTracker.Callback) {
+        displayCallbacks.remove(callback)
+        brightnessCallbacks.remove(callback)
+    }
+
+    fun setDefaultDisplay(displayId: Int) {
+        defaultDisplayId = displayId
+    }
+
+    fun setDisplays(displays: Array<Display>) {
+        allDisplays = displays
+    }
+
+    fun triggerOnDisplayAdded(displayId: Int) {
+        notifyCallbacks({ onDisplayAdded(displayId) }, displayCallbacks)
+    }
+
+    fun triggerOnDisplayRemoved(displayId: Int) {
+        notifyCallbacks({ onDisplayRemoved(displayId) }, displayCallbacks)
+    }
+
+    fun triggerOnDisplayChanged(displayId: Int) {
+        notifyCallbacks({ onDisplayChanged(displayId) }, displayCallbacks)
+    }
+
+    fun triggerOnDisplayBrightnessChanged(displayId: Int) {
+        notifyCallbacks({ onDisplayChanged(displayId) }, brightnessCallbacks)
+    }
+
+    private inline fun notifyCallbacks(
+        crossinline action: DisplayTracker.Callback.() -> Unit,
+        list: List<DisplayTracker.Callback>
+    ) {
+        list.forEach { it.action() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 0dd1fc7..251014f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -67,7 +67,10 @@
         _userHandle = UserHandle.of(_userId)
 
         val copy = callbacks.toList()
-        copy.forEach { it.onUserChanged(_userId, userContext) }
+        copy.forEach {
+            it.onUserChanging(_userId, userContext)
+            it.onUserChanged(_userId, userContext)
+        }
     }
 
     fun onProfileChanged() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index ea5a302..1a8e244 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -39,6 +39,10 @@
     private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
     override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
 
+    private val _userSwitchingInProgress = MutableStateFlow(false)
+    override val userSwitchingInProgress: Flow<Boolean>
+        get() = _userSwitchingInProgress
+
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
 
     private var _isGuestUserAutoCreated: Boolean = false
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
index 4a881a7..fd1b8e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt
@@ -41,7 +41,7 @@
     }
 
     override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? {
-        return data.getOrDefault(key, defValues) as? MutableSet<String>?
+        return (data.getOrDefault(key, defValues) as? Set<String>?)?.toMutableSet()
     }
 
     override fun getInt(key: String, defValue: Int): Int {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index e660e1f2..4b97316 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -23,6 +23,8 @@
 import android.os.UserHandle;
 import android.util.Pair;
 
+import com.android.systemui.settings.UserTracker;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -57,6 +59,11 @@
     }
 
     @Override
+    public UserTracker getUserTracker() {
+        return null;
+    }
+
+    @Override
     public void registerContentObserverForUser(Uri uri, boolean notifyDescendents,
             ContentObserver settingsObserver, int userHandle) {
         List<ContentObserver> observers;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
index 33ef9cf..1baac84 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -46,11 +46,6 @@
     }
 
     @Override
-    public boolean hasEmergencyCryptKeeperText() {
-        return false;
-    }
-
-    @Override
     public boolean isRadioOn() {
         return false;
     }
diff --git a/packages/SystemUI/tools/lint/baseline.xml b/packages/SystemUI/tools/lint/baseline.xml
index 9a2e320..301c9b8 100644
--- a/packages/SystemUI/tools/lint/baseline.xml
+++ b/packages/SystemUI/tools/lint/baseline.xml
@@ -337,17 +337,6 @@
     <issue
         id="Deprecated"
         message="`android:singleLine` is deprecated: Use `maxLines=&quot;1&quot;` instead"
-        errorLine1="        android:singleLine=&quot;true&quot;"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="res/layout/emergency_cryptkeeper_text.xml"
-            line="25"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="Deprecated"
-        message="`android:singleLine` is deprecated: Use `maxLines=&quot;1&quot;` instead"
         errorLine1="                android:singleLine=&quot;true&quot;"
         errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -88997,17 +88986,6 @@
         errorLine1="        android:paddingStart=&quot;6dp&quot;"
         errorLine2="        ~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="res/layout/emergency_cryptkeeper_text.xml"
-            line="24"
-            column="9"/>
-    </issue>
-
-    <issue
-        id="RtlSymmetry"
-        message="When you define `paddingStart` you should probably also define `paddingEnd` for right-to-left symmetry"
-        errorLine1="        android:paddingStart=&quot;6dp&quot;"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~">
-        <location
             file="res/layout/heads_up_status_bar_layout.xml"
             line="43"
             column="9"/>
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 5b45897..97c9ba9 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -79,6 +79,7 @@
         screenStatusProvider.addCallback(screenListener)
         hingeAngleProvider.addCallback(hingeAngleListener)
         rotationChangeProvider.addCallback(rotationListener)
+        activityTypeProvider.init()
     }
 
     override fun stop() {
@@ -87,6 +88,7 @@
         hingeAngleProvider.removeCallback(hingeAngleListener)
         hingeAngleProvider.stop()
         rotationChangeProvider.removeCallback(rotationListener)
+        activityTypeProvider.uninit()
     }
 
     override fun addCallback(listener: FoldUpdatesListener) {
@@ -115,19 +117,17 @@
         }
 
         val isClosing = angle < lastHingeAngle
-        val closingThreshold = getClosingThreshold()
-        val closingThresholdMet = closingThreshold == null || angle < closingThreshold
         val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
         val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
         val screenAvailableEventSent = isUnfoldHandled
 
         if (isClosing // hinge angle should be decreasing since last update
-                && closingThresholdMet // hinge angle is below certain threshold
                 && !closingEventDispatched  // we haven't sent closing event already
                 && !isFullyOpened // do not send closing event if we are in fully opened hinge
                                   // angle range as closing threshold could overlap this range
                 && screenAvailableEventSent // do not send closing event if we are still in
                                             // the process of turning on the inner display
+                && isClosingThresholdMet(angle) // hinge angle is below certain threshold.
         ) {
             notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
         }
@@ -146,6 +146,11 @@
         outputListeners.forEach { it.onHingeAngleUpdate(angle) }
     }
 
+    private fun isClosingThresholdMet(currentAngle: Float) : Boolean {
+        val closingThreshold = getClosingThreshold()
+        return closingThreshold == null || currentAngle < closingThreshold
+    }
+
     /**
      * Fold animation should be started only after the threshold returned here.
      *
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
index d0e6cdc..34e7c38 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
@@ -16,6 +16,11 @@
 
 interface CurrentActivityTypeProvider {
     val isHomeActivity: Boolean?
+
+    /** Starts listening for task updates. */
+    fun init() {}
+    /** Stop listening for task updates. */
+    fun uninit() {}
 }
 
 class EmptyCurrentActivityTypeProvider(override val isHomeActivity: Boolean? = null) :
diff --git a/packages/VpnDialogs/res/values-ky/strings.xml b/packages/VpnDialogs/res/values-ky/strings.xml
index 31f9e2d..ea33e34 100644
--- a/packages/VpnDialogs/res/values-ky/strings.xml
+++ b/packages/VpnDialogs/res/values-ky/strings.xml
@@ -29,7 +29,7 @@
     <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> тармагына ар дайым туташып турсун деп жөндөлгөн, бирок учурда телефонуңуз ага туташа албай жатат. <xliff:g id="VPN_APP_1">%1$s</xliff:g> тармагына кайра туташканга чейин телефонуңуз жалпыга ачык тармакты пайдаланып турат."</string>
     <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> тармагына ар дайым туташып турсун деп жөндөлгөн, бирок учурда телефонуңуз ага туташа албай жатат. VPN тармагына кайра туташмайынча, Интернет жок болот."</string>
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
-    <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN жөндөөлөрүн өзгөртүү"</string>
+    <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN параметрлерин өзгөртүү"</string>
     <string name="configure" msgid="4905518375574791375">"Конфигурациялоо"</string>
     <string name="disconnect" msgid="971412338304200056">"Ажыратуу"</string>
     <string name="open_app" msgid="3717639178595958667">"Колдонмону ачуу"</string>
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 3fa0ab6..e6abc4c 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -391,7 +391,7 @@
     private boolean takeScreenshot() {
         ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
                 ? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
-        screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+        screenshotHelper.takeScreenshot(
                 WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
                 new Handler(Looper.getMainLooper()), null);
         return true;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c4333d9..8a42933 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3813,6 +3813,13 @@
         final boolean includeSharedProfile =
                 (flags & StorageManager.FLAG_INCLUDE_SHARED_PROFILE) != 0;
 
+        // When the caller is the app actually hosting external storage, we
+        // should never attempt to augment the actual storage volume state,
+        // otherwise we risk confusing it with race conditions as users go
+        // through various unlocked states
+        final boolean callerIsMediaStore = UserHandle.isSameApp(callingUid,
+                mMediaStoreAuthorityAppId);
+
         // Only Apps with MANAGE_EXTERNAL_STORAGE should call the API with includeSharedProfile
         if (includeSharedProfile) {
             try {
@@ -3825,8 +3832,13 @@
                 // Checking first entry in packagesFromUid is enough as using "sharedUserId"
                 // mechanism is rare and discouraged. Also, Apps that share same UID share the same
                 // permissions.
-                if (!mStorageManagerInternal.hasExternalStorageAccess(callingUid,
-                        packagesFromUid[0])) {
+                // Allowing Media Provider is an exception, Media Provider process should be allowed
+                // to query users across profiles, even without MANAGE_EXTERNAL_STORAGE access.
+                // Note that ordinarily Media provider process has the above permission, but if they
+                // are revoked, Storage Volume(s) should still be returned.
+                if (!callerIsMediaStore
+                        && !mStorageManagerInternal.hasExternalStorageAccess(callingUid,
+                                packagesFromUid[0])) {
                     throw new SecurityException("Only File Manager Apps permitted");
                 }
             } catch (RemoteException re) {
@@ -3839,13 +3851,6 @@
         // point
         final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
 
-        // When the caller is the app actually hosting external storage, we
-        // should never attempt to augment the actual storage volume state,
-        // otherwise we risk confusing it with race conditions as users go
-        // through various unlocked states
-        final boolean callerIsMediaStore = UserHandle.isSameApp(callingUid,
-                mMediaStoreAuthorityAppId);
-
         final boolean userIsDemo;
         final boolean userKeyUnlocked;
         final boolean storagePermission;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 19b5cc9..1428fa8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8196,15 +8196,13 @@
                 t.traceEnd();
             }
 
+            boolean isBootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
+
             // Some systems - like automotive - will explicitly unlock system user then switch
-            // to a secondary user. Hence, we don't want to send duplicate broadcasts for
-            // the system user here.
+            // to a secondary user.
             // TODO(b/242195409): this workaround shouldn't be necessary once we move
             // the headless-user start logic to UserManager-land.
-            final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
-                    && !UserManager.isHeadlessSystemUserMode();
-
-            if (isBootingSystemUser) {
+            if (isBootingSystemUser && !UserManager.isHeadlessSystemUserMode()) {
                 t.traceBegin("startHomeOnAllDisplays");
                 mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
                 t.traceEnd();
@@ -8216,6 +8214,10 @@
 
 
             if (isBootingSystemUser) {
+                // Need to send the broadcasts for the system user here because
+                // UserController#startUserInternal will not send them for the system user starting,
+                // It checks if the user state already exists, which is always the case for the
+                // system user.
                 t.traceBegin("sendUserStartBroadcast");
                 final int callingUid = Binder.getCallingUid();
                 final int callingPid = Binder.getCallingPid();
@@ -14653,6 +14655,17 @@
                     throw new SecurityException(msg);
                 }
             }
+            if (!Build.IS_DEBUGGABLE && callingUid != ROOT_UID && callingUid != SHELL_UID
+                    && callingUid != SYSTEM_UID && !hasActiveInstrumentationLocked(callingPid)) {
+                // If it's not debug build and not called from root/shell/system uid, reject it.
+                final String msg = "Permission Denial: instrumentation test "
+                        + className + " from pid=" + callingPid + ", uid=" + callingUid
+                        + ", pkgName=" + getPackageNameByPid(callingPid)
+                        + " not allowed because it's not started from SHELL";
+                Slog.wtfQuiet(TAG, msg);
+                reportStartInstrumentationFailureLocked(watcher, className, msg);
+                throw new SecurityException(msg);
+            }
 
             boolean disableHiddenApiChecks = ai.usesNonSdkApi()
                     || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0;
@@ -14875,6 +14888,29 @@
         }
     }
 
+    @GuardedBy("this")
+    private boolean hasActiveInstrumentationLocked(int pid) {
+        if (pid == 0) {
+            return false;
+        }
+        synchronized (mPidsSelfLocked) {
+            ProcessRecord process = mPidsSelfLocked.get(pid);
+            return process != null && process.getActiveInstrumentation() != null;
+        }
+    }
+
+    private String getPackageNameByPid(int pid) {
+        synchronized (mPidsSelfLocked) {
+            final ProcessRecord app = mPidsSelfLocked.get(pid);
+
+            if (app != null && app.info != null) {
+                return app.info.packageName;
+            }
+
+            return null;
+        }
+    }
+
     private boolean isCallerShell() {
         final int callingUid = Binder.getCallingUid();
         return callingUid == SHELL_UID || callingUid == ROOT_UID;
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 8624ee0..bda60ff 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -379,16 +379,11 @@
                 resolvedType = key.requestResolvedType;
             }
 
-            // Apply any launch flags from the ActivityOptions. This is used only by SystemUI
-            // to ensure that we can launch the pending intent with a consistent launch mode even
-            // if the provided PendingIntent is immutable (ie. to force an activity to launch into
-            // a new task, or to launch multiple instances if supported by the app)
+            // Apply any launch flags from the ActivityOptions. This is to ensure that the caller
+            // can specify a consistent launch mode even if the PendingIntent is immutable
             final ActivityOptions opts = ActivityOptions.fromBundle(options);
             if (opts != null) {
-                // TODO(b/254490217): Move this check into SafeActivityOptions
-                if (controller.mAtmInternal.isCallerRecents(Binder.getCallingUid())) {
-                    finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
-                }
+                finalIntent.addFlags(opts.getPendingIntentLaunchFlags());
             }
 
             // Extract options before clearing calling identity
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 3584f16..ac78228 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -51,6 +51,7 @@
 import android.app.GameModeInfo;
 import android.app.GameState;
 import android.app.IGameManagerService;
+import android.app.IUidObserver;
 import android.app.compat.PackageOverride;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -118,6 +119,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Service to manage game related features.
@@ -171,40 +173,19 @@
     private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
     @Nullable
     private final GameServiceController mGameServiceController;
+    private final Object mUidObserverLock = new Object();
+    @VisibleForTesting
+    @Nullable
+    final UidObserver mUidObserver;
+    @GuardedBy("mUidObserverLock")
+    private final Set<Integer> mForegroundGameUids = new HashSet<>();
 
     public GameManagerService(Context context) {
         this(context, createServiceThread().getLooper());
     }
 
     GameManagerService(Context context, Looper looper) {
-        mContext = context;
-        mHandler = new SettingsHandler(looper);
-        mPackageManager = mContext.getPackageManager();
-        mUserManager = mContext.getSystemService(UserManager.class);
-        mPlatformCompat = IPlatformCompat.Stub.asInterface(
-                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
-        mSystemDir = new File(Environment.getDataDirectory(), "system");
-        mSystemDir.mkdirs();
-        FileUtils.setPermissions(mSystemDir.toString(),
-                FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
-                -1, -1);
-        mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
-                                                     GAME_MODE_INTERVENTION_LIST_FILE_NAME));
-        FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
-                FileUtils.S_IRUSR | FileUtils.S_IWUSR
-                        | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-                -1, -1);
-        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
-            mGameServiceController = new GameServiceController(
-                    context, BackgroundThread.getExecutor(),
-                    new GameServiceProviderSelectorImpl(
-                            context.getResources(),
-                            context.getPackageManager()),
-                    new GameServiceProviderInstanceFactoryImpl(context));
-        } else {
-            mGameServiceController = null;
-        }
+        this(context, looper, Environment.getDataDirectory());
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@@ -237,6 +218,14 @@
         } else {
             mGameServiceController = null;
         }
+        mUidObserver = new UidObserver();
+        try {
+            ActivityManager.getService().registerUidObserver(mUidObserver,
+                    ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
+                    ActivityManager.PROCESS_STATE_UNKNOWN, null);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Could not register UidObserver");
+        }
     }
 
     @Override
@@ -1874,4 +1863,66 @@
      * load dynamic library for frame rate overriding JNI calls
      */
     private static native void nativeSetOverrideFrameRate(int uid, float frameRate);
+
+    final class UidObserver extends IUidObserver.Stub {
+        @Override
+        public void onUidIdle(int uid, boolean disabled) {}
+
+        @Override
+        public void onUidGone(int uid, boolean disabled) {
+            synchronized (mUidObserverLock) {
+                disableGameMode(uid);
+            }
+        }
+
+        @Override
+        public void onUidActive(int uid) {}
+
+        @Override
+        public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+            synchronized (mUidObserverLock) {
+                if (ActivityManager.isProcStateBackground(procState)) {
+                    disableGameMode(uid);
+                    return;
+                }
+
+                final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+                if (packages == null || packages.length == 0) {
+                    return;
+                }
+
+                final int userId = mContext.getUserId();
+                if (!Arrays.stream(packages).anyMatch(p -> isPackageGame(p, userId))) {
+                    return;
+                }
+
+                if (mForegroundGameUids.isEmpty()) {
+                    Slog.v(TAG, "Game power mode ON (process state was changed to foreground)");
+                    mPowerManagerInternal.setPowerMode(Mode.GAME, true);
+                }
+                mForegroundGameUids.add(uid);
+            }
+        }
+
+        private void disableGameMode(int uid) {
+            synchronized (mUidObserverLock) {
+                if (!mForegroundGameUids.contains(uid)) {
+                    return;
+                }
+                mForegroundGameUids.remove(uid);
+                if (!mForegroundGameUids.isEmpty()) {
+                    return;
+                }
+                Slog.v(TAG,
+                        "Game power mode OFF (process remomved or state changed to background)");
+                mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+            }
+        }
+
+        @Override
+        public void onUidCachedChanged(int uid, boolean cached) {}
+
+        @Override
+        public void onUidProcAdjChanged(int uid) {}
+    }
 }
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 4fd739ca..4b0ae1b 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.server.app;
 
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,7 +36,6 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.games.CreateGameSessionRequest;
@@ -50,7 +52,6 @@
 import android.util.Slog;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost.SurfacePackage;
-import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -59,6 +60,7 @@
 import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
@@ -861,8 +863,6 @@
                 Slog.w(TAG, "Could not get bitmap for id: " + taskId);
                 callback.complete(GameScreenshotResult.createInternalErrorResult());
             } else {
-                final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(
-                        bitmap);
                 final RunningTaskInfo runningTaskInfo =
                         mGameTaskInfoProvider.getRunningTaskInfo(taskId);
                 if (runningTaskInfo == null) {
@@ -877,11 +877,17 @@
                         callback.complete(GameScreenshotResult.createSuccessResult());
                     }
                 };
-                mScreenshotHelper.provideScreenshot(bundle, crop, Insets.NONE, taskId,
-                        mUserHandle.getIdentifier(), gameSessionRecord.getComponentName(),
-                        WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
-                        BackgroundThread.getHandler(),
-                        completionConsumer);
+                ScreenshotRequest request = new ScreenshotRequest.Builder(
+                        TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+                        .setTopComponent(gameSessionRecord.getComponentName())
+                        .setTaskId(taskId)
+                        .setUserId(mUserHandle.getIdentifier())
+                        .setBitmap(bitmap)
+                        .setBoundsOnScreen(crop)
+                        .setInsets(Insets.NONE)
+                        .build();
+                mScreenshotHelper.takeScreenshot(
+                        request, BackgroundThread.getHandler(), completionConsumer);
             }
         });
     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 278c98f..0589cfc 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -56,6 +56,7 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -434,6 +435,48 @@
         return device;
     }
 
+    private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = {
+            AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+            AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+            AudioDeviceInfo.TYPE_WIRED_HEADSET,
+            AudioDeviceInfo.TYPE_USB_HEADSET,
+            AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+            AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+            AudioDeviceInfo.TYPE_HEARING_AID,
+            AudioDeviceInfo.TYPE_BLE_HEADSET,
+            AudioDeviceInfo.TYPE_USB_DEVICE,
+            AudioDeviceInfo.TYPE_BLE_SPEAKER,
+            AudioDeviceInfo.TYPE_LINE_ANALOG,
+            AudioDeviceInfo.TYPE_HDMI,
+            AudioDeviceInfo.TYPE_AUX_LINE
+    };
+
+    /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+        for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
+            if (device.getType() == type) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() {
+        ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>();
+        AudioDeviceInfo[] allDevices =
+                AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : allDevices) {
+            if (isValidCommunicationDevice(device)) {
+                commDevices.add(device);
+            }
+        }
+        return commDevices;
+    }
+
+    private @Nullable AudioDeviceInfo getCommunicationDeviceOfType(int type) {
+        return getAvailableCommunicationDevices().stream().filter(d -> d.getType() == type)
+                .findFirst().orElse(null);
+    }
+
     /**
      * Returns the device currently requested for communication use case.
      * @return AudioDeviceInfo the requested device for communication.
@@ -441,7 +484,29 @@
     /* package */ AudioDeviceInfo getCommunicationDevice() {
         synchronized (mDeviceStateLock) {
             updateActiveCommunicationDevice();
-            return mActiveCommunicationDevice;
+            AudioDeviceInfo device = mActiveCommunicationDevice;
+            // make sure we return a valid communication device (i.e. a device that is allowed by
+            // setCommunicationDevice()) for consistency.
+            if (device != null) {
+                // a digital dock is used instead of the speaker in speakerphone mode and should
+                // be reflected as such
+                if (device.getType() == AudioDeviceInfo.TYPE_DOCK) {
+                    device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+                }
+            }
+            // Try to default to earpiece when current communication device is not valid. This can
+            // happen for instance if no call is active. If no earpiece device is available take the
+            // first valid communication device
+            if (device == null || !AudioDeviceBroker.isValidCommunicationDevice(device)) {
+                device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                if (device == null) {
+                    List<AudioDeviceInfo> commDevices = getAvailableCommunicationDevices();
+                    if (!commDevices.isEmpty()) {
+                        device = commDevices.get(0);
+                    }
+                }
+            }
+            return device;
         }
     }
 
@@ -918,8 +983,8 @@
 
     @GuardedBy("mDeviceStateLock")
     private void dispatchCommunicationDevice() {
-        int portId = (mActiveCommunicationDevice == null) ? 0
-                : mActiveCommunicationDevice.getId();
+        AudioDeviceInfo device = getCommunicationDevice();
+        int portId = device != null ? device.getId() : 0;
         if (portId == mCurCommunicationPortId) {
             return;
         }
@@ -936,6 +1001,7 @@
         mCommDevDispatchers.finishBroadcast();
     }
 
+
     //---------------------------------------------------------------------
     // Communication with (to) AudioService
     //TODO check whether the AudioService methods are candidates to move here
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1bd8f1e..b8e3a3a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1253,6 +1253,20 @@
                 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
     }
 
+    private void initVolumeStreamStates() {
+        int numStreamTypes = AudioSystem.getNumStreamTypes();
+        synchronized (VolumeStreamState.class) {
+            for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+                VolumeStreamState streamState = mStreamStates[streamType];
+                int groupId = getVolumeGroupForStreamType(streamType);
+                if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
+                        && sVolumeGroupStates.indexOfKey(groupId) >= 0) {
+                    streamState.setVolumeGroupState(sVolumeGroupStates.get(groupId));
+                }
+            }
+        }
+    }
+
     /**
      * Separating notification volume from ring is NOT of aliasing the corresponding streams
      * @param properties
@@ -1282,6 +1296,8 @@
         // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
         // relies on audio policy having correct ranges for volume indexes.
         mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+        // Link VGS on VSS
+        initVolumeStreamStates();
 
         // Call setRingerModeInt() to apply correct mute
         // state on streams affected by ringer mode.
@@ -2178,19 +2194,19 @@
                 AudioSystem.STREAM_ASSISTANT : AudioSystem.STREAM_MUSIC;
 
         if (mIsSingleVolume) {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION.clone();
             dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
         } else if (mUseVolumeGroupAliases) {
-            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE;
+            mStreamVolumeAlias = STREAM_VOLUME_ALIAS_NONE.clone();
             dtmfStreamAlias = AudioSystem.STREAM_DTMF;
         } else {
             switch (mPlatformType) {
                 case AudioSystem.PLATFORM_VOICE:
-                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE;
+                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE.clone();
                     dtmfStreamAlias = AudioSystem.STREAM_RING;
                     break;
                 default:
-                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
+                    mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT.clone();
                     dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
             }
             if (!mNotifAliasRing) {
@@ -3340,15 +3356,7 @@
                 } else {
                     state = direction == AudioManager.ADJUST_MUTE;
                 }
-                for (int stream = 0; stream < mStreamStates.length; stream++) {
-                    if (streamTypeAlias == mStreamVolumeAlias[stream]) {
-                        if (!(readCameraSoundForced()
-                                    && (mStreamStates[stream].getStreamType()
-                                        == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
-                            mStreamStates[stream].mute(state);
-                        }
-                    }
-                }
+                muteAliasStreams(streamTypeAlias, state);
             } else if ((direction == AudioManager.ADJUST_RAISE) &&
                     !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
                 Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
@@ -3363,7 +3371,7 @@
                     // Unmute the stream if it was previously muted
                     if (direction == AudioManager.ADJUST_RAISE) {
                         // unmute immediately for volume up
-                        streamState.mute(false);
+                        muteAliasStreams(streamTypeAlias, false);
                     } else if (direction == AudioManager.ADJUST_LOWER) {
                         if (mIsSingleVolume) {
                             sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
@@ -3489,6 +3497,43 @@
         sendVolumeUpdate(streamType, oldIndex, newIndex, flags, device);
     }
 
+    /**
+     * Loops on aliasted stream, update the mute cache attribute of each
+     * {@see AudioService#VolumeStreamState}, and then apply the change.
+     * It prevents to unnecessary {@see AudioSystem#setStreamVolume} done for each stream
+     * and aliases before mute change changed and after.
+     */
+    private void muteAliasStreams(int streamAlias, boolean state) {
+        synchronized (VolumeStreamState.class) {
+            List<Integer> streamsToMute = new ArrayList<>();
+            for (int stream = 0; stream < mStreamStates.length; stream++) {
+                VolumeStreamState vss = mStreamStates[stream];
+                if (streamAlias == mStreamVolumeAlias[stream] && vss.isMutable()) {
+                    if (!(readCameraSoundForced()
+                            && (vss.getStreamType()
+                                    == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
+                        boolean changed = vss.mute(state, /* apply= */ false);
+                        if (changed) {
+                            streamsToMute.add(stream);
+                        }
+                    }
+                }
+            }
+            streamsToMute.forEach(streamToMute -> {
+                mStreamStates[streamToMute].doMute();
+                broadcastMuteSetting(streamToMute, state);
+            });
+        }
+    }
+
+    private void broadcastMuteSetting(int streamType, boolean isMuted) {
+        // Stream mute changed, fire the intent.
+        Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+        intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
+        intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted);
+        sendBroadcastToAll(intent);
+    }
+
     // Called after a delay when volume down is pressed while muted
     private void onUnmuteStream(int stream, int flags) {
         boolean wasMuted;
@@ -3593,8 +3638,19 @@
         return false;
     }
 
+    /**
+     * Update stream volume, ringer mode and mute status after a volume index change
+     * @param streamType
+     * @param index
+     * @param flags
+     * @param device the device for which the volume is changed
+     * @param caller
+     * @param hasModifyAudioSettings
+     * @param canChangeMute true if the origin of this event is one where the mute state should be
+     *                      updated following the change in volume index
+     */
     private void onSetStreamVolume(int streamType, int index, int flags, int device,
-            String caller, boolean hasModifyAudioSettings) {
+            String caller, boolean hasModifyAudioSettings, boolean canChangeMute) {
         final int stream = mStreamVolumeAlias[streamType];
         setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
         // setting volume on ui sounds stream type also controls silent mode
@@ -3604,11 +3660,10 @@
                     TAG + ".onSetStreamVolume", false /*external*/);
         }
         // setting non-zero volume for a muted stream unmutes the stream and vice versa
-        // (only when changing volume for the current device),
         // except for BT SCO stream where only explicit mute is allowed to comply to BT requirements
-        if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO)
-                && (getDeviceForStream(stream) == device)) {
-            mStreamStates[stream].mute(index == 0);
+        if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) && canChangeMute) {
+            // As adjustStreamVolume with muteAdjust flags mute/unmutes stream and aliased streams.
+            muteAliasStreams(stream, index == 0);
         }
     }
 
@@ -3664,29 +3719,27 @@
     }
 
 
-    /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */
-    public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags,
+    /** @see AudioManager#setVolumeGroupVolumeIndex(int, int, int) */
+    public void setVolumeGroupVolumeIndex(int groupId, int index, int flags,
             String callingPackage, String attributionTag) {
         enforceModifyAudioRoutingPermission();
-        Objects.requireNonNull(attr, "attr must not be null");
-        final int volumeGroup = getVolumeGroupIdForAttributes(attr);
-        if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
-            Log.e(TAG, ": no volume group found for attributes " + attr.toString());
+        if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+            Log.e(TAG, ": no volume group found for id " + groupId);
             return;
         }
-        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+        VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
 
-        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
-                index/*val1*/, flags/*val2*/, callingPackage));
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, vgs.name(),
+                index, flags, callingPackage + ", user " + getCurrentUserId()));
 
         vgs.setVolumeIndex(index, flags);
 
         // For legacy reason, propagate to all streams associated to this volume group
-        for (final int groupedStream : vgs.getLegacyStreamTypes()) {
+        for (int groupedStream : vgs.getLegacyStreamTypes()) {
             try {
                 ensureValidStreamType(groupedStream);
             } catch (IllegalArgumentException e) {
-                Log.d(TAG, "volume group " + volumeGroup + " has internal streams (" + groupedStream
+                Log.d(TAG, "volume group " + groupId + " has internal streams (" + groupedStream
                         + "), do not change associated stream volume");
                 continue;
             }
@@ -3698,7 +3751,7 @@
 
     @Nullable
     private AudioVolumeGroup getAudioVolumeGroupById(int volumeGroupId) {
-        for (final AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) {
+        for (AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) {
             if (avg.getId() == volumeGroupId) {
                 return avg;
             }
@@ -3708,30 +3761,42 @@
         return null;
     }
 
-    /** @see AudioManager#getVolumeIndexForAttributes(attr) */
-    public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+    /** @see AudioManager#getVolumeGroupVolumeIndex(int) */
+    public int getVolumeGroupVolumeIndex(int groupId) {
         enforceModifyAudioRoutingPermission();
-        Objects.requireNonNull(attr, "attr must not be null");
-        final int volumeGroup = getVolumeGroupIdForAttributes(attr);
-        if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
-            throw new IllegalArgumentException("No volume group for attributes " + attr);
+        synchronized (VolumeStreamState.class) {
+            if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+                throw new IllegalArgumentException("No volume group for id " + groupId);
+            }
+            VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+            // Return 0 when muted, not min index since for e.g. Voice Call, it has a non zero
+            // min but it mutable on permission condition.
+            return vgs.isMuted() ? 0 : vgs.getVolumeIndex();
         }
-        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
-        return vgs.getVolumeIndex();
     }
 
-    /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
-    public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+    /** @see AudioManager#getVolumeGroupMaxVolumeIndex(int) */
+    public int getVolumeGroupMaxVolumeIndex(int groupId) {
         enforceModifyAudioRoutingPermission();
-        Objects.requireNonNull(attr, "attr must not be null");
-        return AudioSystem.getMaxVolumeIndexForAttributes(attr);
+        synchronized (VolumeStreamState.class) {
+            if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+                throw new IllegalArgumentException("No volume group for id " + groupId);
+            }
+            VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+            return vgs.getMaxIndex();
+        }
     }
 
-    /** @see AudioManager#getMinVolumeIndexForAttributes(attr) */
-    public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
+    /** @see AudioManager#getVolumeGroupMinVolumeIndex(int) */
+    public int getVolumeGroupMinVolumeIndex(int groupId) {
         enforceModifyAudioRoutingPermission();
-        Objects.requireNonNull(attr, "attr must not be null");
-        return AudioSystem.getMinVolumeIndexForAttributes(attr);
+        synchronized (VolumeStreamState.class) {
+            if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+                throw new IllegalArgumentException("No volume group for id " + groupId);
+            }
+            VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+            return vgs.getMinIndex();
+        }
     }
 
     /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
@@ -3808,6 +3873,70 @@
                 callingPackage, /*attributionTag*/ null);
     }
 
+    /** @see AudioManager#adjustVolumeGroupVolume(int, int, int) */
+    public void adjustVolumeGroupVolume(int groupId, int direction, int flags,
+                                        String callingPackage) {
+        ensureValidDirection(direction);
+        if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+            Log.e(TAG, ": no volume group found for id " + groupId);
+            return;
+        }
+        VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+        // For compatibility reason, use stream API if group linked to a valid stream
+        boolean fallbackOnStream = false;
+        for (int stream : vgs.getLegacyStreamTypes()) {
+            try {
+                ensureValidStreamType(stream);
+            } catch (IllegalArgumentException e) {
+                Log.d(TAG, "volume group " + groupId + " has internal streams (" + stream
+                        + "), do not change associated stream volume");
+                continue;
+            }
+            // Note: Group and Stream does not share same convention, 0 is mute for stream,
+            // min index is acting as mute for Groups
+            if (vgs.isVssMuteBijective(stream)) {
+                adjustStreamVolume(stream, direction, flags, callingPackage);
+                if (isMuteAdjust(direction)) {
+                    // will be propagated to all aliased streams
+                    return;
+                }
+                fallbackOnStream = true;
+            }
+        }
+        if (fallbackOnStream) {
+            // Handled by at least one stream, will be propagated to group, bailing out.
+            return;
+        }
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_GROUP_VOL, vgs.name(),
+                direction, flags, callingPackage));
+        vgs.adjustVolume(direction, flags);
+    }
+
+    /** @see AudioManager#getLastAudibleVolumeGroupVolume(int) */
+    public int getLastAudibleVolumeGroupVolume(int groupId) {
+        enforceQueryStatePermission();
+        synchronized (VolumeStreamState.class) {
+            if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+                Log.e(TAG, ": no volume group found for id " + groupId);
+                return 0;
+            }
+            VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+            return vgs.getVolumeIndex();
+        }
+    }
+
+    /** @see AudioManager#isVolumeGroupMuted(int) */
+    public boolean isVolumeGroupMuted(int groupId) {
+        synchronized (VolumeStreamState.class) {
+            if (sVolumeGroupStates.indexOfKey(groupId) < 0) {
+                Log.e(TAG, ": no volume group found for id " + groupId);
+                return false;
+            }
+            VolumeGroupState vgs = sVolumeGroupStates.get(groupId);
+            return vgs.isMuted();
+        }
+    }
+
     /** @see AudioManager#setStreamVolume(int, int, int)
      * Part of service interface, check permissions here */
     public void setStreamVolumeWithAttribution(int streamType, int index, int flags,
@@ -4247,7 +4376,10 @@
                 mPendingVolumeCommand = new StreamVolumeCommand(
                                                     streamType, index, flags, device);
             } else {
-                onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
+                onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings,
+                        // ada is non-null when called from setDeviceVolume,
+                        // which shouldn't update the mute state
+                        ada == null /*canChangeMute*/);
                 index = mStreamStates[streamType].getIndex(device);
             }
         }
@@ -4264,31 +4396,6 @@
         }
     }
 
-
-
-    private int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
-        Objects.requireNonNull(attributes, "attributes must not be null");
-        int volumeGroupId = getVolumeGroupIdForAttributesInt(attributes);
-        if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
-            return volumeGroupId;
-        }
-        // The default volume group is the one hosted by default product strategy, i.e.
-        // supporting Default Attributes
-        return getVolumeGroupIdForAttributesInt(AudioProductStrategy.getDefaultAttributes());
-    }
-
-    private int getVolumeGroupIdForAttributesInt(@NonNull AudioAttributes attributes) {
-        Objects.requireNonNull(attributes, "attributes must not be null");
-        for (final AudioProductStrategy productStrategy :
-                AudioProductStrategy.getAudioProductStrategies()) {
-            int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes);
-            if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
-                return volumeGroupId;
-            }
-        }
-        return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
-    }
-
     private void dispatchAbsoluteVolumeChanged(int streamType, AbsoluteVolumeDeviceInfo deviceInfo,
             int index) {
         VolumeInfo volumeInfo = deviceInfo.getMatchingVolumeInfoForStream(streamType);
@@ -4321,7 +4428,6 @@
         }
     }
 
-
     // No ringer or zen muted stream volumes can be changed unless it'll exit dnd
     private boolean volumeAdjustmentAllowedByDnd(int streamTypeAlias, int flags) {
         switch (mNm.getZenMode()) {
@@ -5013,7 +5119,7 @@
     }
 
     private void setRingerMode(int ringerMode, String caller, boolean external) {
-        if (mUseFixedVolume || mIsSingleVolume) {
+        if (mUseFixedVolume || mIsSingleVolume || mUseVolumeGroupAliases) {
             return;
         }
         if (caller == null || caller.length() == 0) {
@@ -5096,7 +5202,8 @@
             if (!shouldMute) {
                 // unmute
                 // ring and notifications volume should never be 0 when not silenced
-                if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
+                if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING
+                        || mStreamVolumeAlias[streamType] == AudioSystem.STREAM_NOTIFICATION) {
                     synchronized (VolumeStreamState.class) {
                         final VolumeStreamState vss = mStreamStates[streamType];
                         for (int i = 0; i < vss.mIndexMap.size(); i++) {
@@ -5821,6 +5928,8 @@
             }
         }
 
+        readVolumeGroupsSettings(userSwitch);
+
         // apply new ringer mode before checking volume for alias streams so that streams
         // muted by ringer mode have the correct volume
         setRingerModeInt(getRingerModeInternal(), false);
@@ -5838,54 +5947,22 @@
             }
         }
 
-        readVolumeGroupsSettings();
-
         if (DEBUG_VOL) {
             Log.d(TAG, "Restoring device volume behavior");
         }
         restoreDeviceVolumeBehavior();
     }
 
-    private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = {
-        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
-        AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
-        AudioDeviceInfo.TYPE_WIRED_HEADSET,
-        AudioDeviceInfo.TYPE_USB_HEADSET,
-        AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
-        AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
-        AudioDeviceInfo.TYPE_HEARING_AID,
-        AudioDeviceInfo.TYPE_BLE_HEADSET,
-        AudioDeviceInfo.TYPE_USB_DEVICE,
-        AudioDeviceInfo.TYPE_BLE_SPEAKER,
-        AudioDeviceInfo.TYPE_LINE_ANALOG,
-        AudioDeviceInfo.TYPE_HDMI,
-        AudioDeviceInfo.TYPE_AUX_LINE
-    };
-
-    private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
-        for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
-            if (device.getType() == type) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /** @see AudioManager#getAvailableCommunicationDevices(int) */
     public int[] getAvailableCommunicationDeviceIds() {
-        ArrayList<Integer> deviceIds = new ArrayList<>();
-        AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
-        for (AudioDeviceInfo device : devices) {
-            if (isValidCommunicationDevice(device)) {
-                deviceIds.add(device.getId());
-            }
-        }
-        return deviceIds.stream().mapToInt(Integer::intValue).toArray();
+        List<AudioDeviceInfo> commDevices = AudioDeviceBroker.getAvailableCommunicationDevices();
+        return commDevices.stream().mapToInt(AudioDeviceInfo::getId).toArray();
     }
-        /**
-         * @see AudioManager#setCommunicationDevice(int)
-         * @see AudioManager#clearCommunicationDevice()
-         */
+
+    /**
+     * @see AudioManager#setCommunicationDevice(int)
+     * @see AudioManager#clearCommunicationDevice()
+     */
     public boolean setCommunicationDevice(IBinder cb, int portId) {
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
@@ -5894,9 +5971,10 @@
         if (portId != 0) {
             device = AudioManager.getDeviceForPortId(portId, AudioManager.GET_DEVICES_OUTPUTS);
             if (device == null) {
-                throw new IllegalArgumentException("invalid portID " + portId);
+                Log.w(TAG, "setCommunicationDevice: invalid portID " + portId);
+                return false;
             }
-            if (!isValidCommunicationDevice(device)) {
+            if (!AudioDeviceBroker.isValidCommunicationDevice(device)) {
                 throw new IllegalArgumentException("invalid device type " + device.getType());
             }
         }
@@ -5939,13 +6017,15 @@
 
     /** @see AudioManager#getCommunicationDevice() */
     public int getCommunicationDevice() {
+        int deviceId = 0;
         final long ident = Binder.clearCallingIdentity();
-        AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice();
-        Binder.restoreCallingIdentity(ident);
-        if (device == null) {
-            return 0;
+        try {
+            AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice();
+            deviceId = device != null ? device.getId() : 0;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
-        return device.getId();
+        return deviceId;
     }
 
     /** @see AudioManager#addOnCommunicationDeviceChangedListener(
@@ -7313,6 +7393,7 @@
             try {
                 // if no valid attributes, this volume group is not controllable, throw exception
                 ensureValidAttributes(avg);
+                sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
             } catch (IllegalArgumentException e) {
                 // Volume Groups without attributes are not controllable through set/get volume
                 // using attributes. Do not append them.
@@ -7321,11 +7402,10 @@
                 }
                 continue;
             }
-            sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
         }
         for (int i = 0; i < sVolumeGroupStates.size(); i++) {
             final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
-            vgs.applyAllVolumes();
+            vgs.applyAllVolumes(/* userSwitch= */ false);
         }
     }
 
@@ -7338,14 +7418,22 @@
         }
     }
 
-    private void readVolumeGroupsSettings() {
-        if (DEBUG_VOL) {
-            Log.v(TAG, "readVolumeGroupsSettings");
-        }
-        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
-            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
-            vgs.readSettings();
-            vgs.applyAllVolumes();
+    private void readVolumeGroupsSettings(boolean userSwitch) {
+        synchronized (mSettingsLock) {
+            synchronized (VolumeStreamState.class) {
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "readVolumeGroupsSettings userSwitch=" + userSwitch);
+                }
+                for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+                    VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+                    // as for STREAM_MUSIC, preserve volume from one user to the next.
+                    if (!(userSwitch && vgs.isMusic())) {
+                        vgs.clearIndexCache();
+                        vgs.readSettings();
+                    }
+                    vgs.applyAllVolumes(userSwitch);
+                }
+            }
         }
     }
 
@@ -7356,7 +7444,7 @@
         }
         for (int i = 0; i < sVolumeGroupStates.size(); i++) {
             final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
-            vgs.applyAllVolumes();
+            vgs.applyAllVolumes(false/*userSwitch*/);
         }
     }
 
@@ -7369,17 +7457,34 @@
         }
     }
 
+    private static boolean isCallStream(int stream) {
+        return stream == AudioSystem.STREAM_VOICE_CALL
+                || stream == AudioSystem.STREAM_BLUETOOTH_SCO;
+    }
+
+    private static int getVolumeGroupForStreamType(int stream) {
+        AudioAttributes attributes =
+                AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(stream);
+        if (attributes.equals(new AudioAttributes.Builder().build())) {
+            return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
+        }
+        return AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+                attributes, /* fallbackOnDefault= */ false);
+    }
+
     // NOTE: Locking order for synchronized objects related to volume management:
     //  1     mSettingsLock
-    //  2       VolumeGroupState.class
+    //  2       VolumeStreamState.class
     private class VolumeGroupState {
         private final AudioVolumeGroup mAudioVolumeGroup;
         private final SparseIntArray mIndexMap = new SparseIntArray(8);
         private int mIndexMin;
         private int mIndexMax;
-        private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
+        private boolean mHasValidStreamType = false;
         private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
         private AudioAttributes mAudioAttributes = AudioProductStrategy.getDefaultAttributes();
+        private boolean mIsMuted = false;
+        private final String mSettingName;
 
         // No API in AudioSystem to get a device from strategy or from attributes.
         // Need a valid public stream type to use current API getDeviceForStream
@@ -7393,20 +7498,22 @@
                 Log.v(TAG, "VolumeGroupState for " + avg.toString());
             }
             // mAudioAttributes is the default at this point
-            for (final AudioAttributes aa : avg.getAudioAttributes()) {
+            for (AudioAttributes aa : avg.getAudioAttributes()) {
                 if (!aa.equals(mAudioAttributes)) {
                     mAudioAttributes = aa;
                     break;
                 }
             }
-            final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+            int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+            String streamSettingName = "";
             if (streamTypes.length != 0) {
                 // Uses already initialized MIN / MAX if a stream type is attached to group
-                mLegacyStreamType = streamTypes[0];
-                for (final int streamType : streamTypes) {
+                for (int streamType : streamTypes) {
                     if (streamType != AudioSystem.STREAM_DEFAULT
                             && streamType < AudioSystem.getNumStreamTypes()) {
                         mPublicStreamType = streamType;
+                        mHasValidStreamType = true;
+                        streamSettingName = System.VOLUME_SETTINGS_INT[mPublicStreamType];
                         break;
                     }
                 }
@@ -7416,10 +7523,10 @@
                 mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
                 mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
             } else {
-                Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
+                throw new IllegalArgumentException("volume group: " + mAudioVolumeGroup.name()
                         + " has neither valid attributes nor valid stream types assigned");
-                return;
             }
+            mSettingName = !streamSettingName.isEmpty() ? streamSettingName : ("volume_" + name());
             // Load volume indexes from data base
             readSettings();
         }
@@ -7432,40 +7539,149 @@
             return mAudioVolumeGroup.name();
         }
 
+        /**
+         * Volume group with non null minimum index are considered as non mutable, thus
+         * bijectivity is broken with potential associated stream type.
+         * VOICE_CALL stream has minVolumeIndex > 0  but can be muted directly by an
+         * app that has MODIFY_PHONE_STATE permission.
+         */
+        private boolean isVssMuteBijective(int stream) {
+            return isStreamAffectedByMute(stream)
+                    && (getMinIndex() == (mStreamStates[stream].mIndexMin + 5) / 10)
+                    && (getMinIndex() == 0 || isCallStream(stream));
+        }
+
+        private boolean isMutable() {
+            return mIndexMin == 0 || (mHasValidStreamType && isVssMuteBijective(mPublicStreamType));
+        }
+        /**
+         * Mute/unmute the volume group
+         * @param muted the new mute state
+         */
+        @GuardedBy("AudioService.VolumeStreamState.class")
+        public boolean mute(boolean muted) {
+            if (!isMutable()) {
+                // Non mutable volume group
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "invalid mute on unmutable volume group " + name());
+                }
+                return false;
+            }
+            boolean changed = (mIsMuted != muted);
+            // As for VSS, mute shall apply minIndex to all devices found in IndexMap and default.
+            if (changed) {
+                mIsMuted = muted;
+                applyAllVolumes(false /*userSwitch*/);
+            }
+            return changed;
+        }
+
+        public boolean isMuted() {
+            return mIsMuted;
+        }
+
+        public void adjustVolume(int direction, int flags) {
+            synchronized (VolumeStreamState.class) {
+                int device = getDeviceForVolume();
+                int previousIndex = getIndex(device);
+                if (isMuteAdjust(direction) && !isMutable()) {
+                    // Non mutable volume group
+                    if (DEBUG_VOL) {
+                        Log.d(TAG, "invalid mute on unmutable volume group " + name());
+                    }
+                    return;
+                }
+                switch (direction) {
+                    case AudioManager.ADJUST_TOGGLE_MUTE: {
+                        // Note: If muted by volume 0, unmute will restore volume 0.
+                        mute(!mIsMuted);
+                        break;
+                    }
+                    case AudioManager.ADJUST_UNMUTE:
+                        // Note: If muted by volume 0, unmute will restore volume 0.
+                        mute(false);
+                        break;
+                    case AudioManager.ADJUST_MUTE:
+                        // May be already muted by setvolume 0, prevent from setting same value
+                        if (previousIndex != 0) {
+                            // bypass persist
+                            mute(true);
+                        }
+                        mIsMuted = true;
+                        break;
+                    case AudioManager.ADJUST_RAISE:
+                        // As for stream, RAISE during mute will increment the index
+                        setVolumeIndex(Math.min(previousIndex + 1, mIndexMax),  device, flags);
+                        break;
+                    case AudioManager.ADJUST_LOWER:
+                        // For stream, ADJUST_LOWER on a muted VSS is a no-op
+                        // If we decide to unmute on ADJUST_LOWER, cannot fallback on
+                        // adjustStreamVolume for group associated to legacy stream type
+                        if (isMuted() && previousIndex != 0) {
+                            mute(false);
+                        } else {
+                            int newIndex = Math.max(previousIndex - 1, mIndexMin);
+                            setVolumeIndex(newIndex, device, flags);
+                        }
+                        break;
+                }
+            }
+        }
+
         public int getVolumeIndex() {
-            return getIndex(getDeviceForVolume());
+            synchronized (VolumeStreamState.class) {
+                return getIndex(getDeviceForVolume());
+            }
         }
 
         public void setVolumeIndex(int index, int flags) {
-            if (mUseFixedVolume) {
-                return;
+            synchronized (VolumeStreamState.class) {
+                if (mUseFixedVolume) {
+                    return;
+                }
+                setVolumeIndex(index, getDeviceForVolume(), flags);
             }
-            setVolumeIndex(index, getDeviceForVolume(), flags);
         }
 
+        @GuardedBy("AudioService.VolumeStreamState.class")
         private void setVolumeIndex(int index, int device, int flags) {
-            // Set the volume index
-            setVolumeIndexInt(index, device, flags);
-
-            // Update local cache
-            mIndexMap.put(device, index);
-
-            // update data base - post a persist volume group msg
-            sendMsg(mAudioHandler,
-                    MSG_PERSIST_VOLUME_GROUP,
-                    SENDMSG_QUEUE,
-                    device,
-                    0,
-                    this,
-                    PERSIST_DELAY);
+            // Update cache & persist (muted by volume 0 shall be persisted)
+            updateVolumeIndex(index, device);
+            // setting non-zero volume for a muted stream unmutes the stream and vice versa,
+            boolean changed = mute(index == 0);
+            if (!changed) {
+                // Set the volume index only if mute operation is a no-op
+                index = getValidIndex(index);
+                setVolumeIndexInt(index, device, flags);
+            }
         }
 
+        @GuardedBy("AudioService.VolumeStreamState.class")
+        public void updateVolumeIndex(int index, int device) {
+            // Filter persistency if already exist and the index has not changed
+            if (mIndexMap.indexOfKey(device) < 0 || mIndexMap.get(device) != index) {
+                // Update local cache
+                mIndexMap.put(device, getValidIndex(index));
+
+                // update data base - post a persist volume group msg
+                sendMsg(mAudioHandler,
+                        MSG_PERSIST_VOLUME_GROUP,
+                        SENDMSG_QUEUE,
+                        device,
+                        0,
+                        this,
+                        PERSIST_DELAY);
+            }
+        }
+
+        @GuardedBy("AudioService.VolumeStreamState.class")
         private void setVolumeIndexInt(int index, int device, int flags) {
             // Reflect mute state of corresponding stream by forcing index to 0 if muted
             // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
             // This allows RX path muting by the audio HAL only when explicitly muted but not when
             // index is just set to 0 to repect BT requirements
-            if (mStreamStates[mPublicStreamType].isFullyMuted()) {
+            if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)
+                    && mStreamStates[mPublicStreamType].isFullyMuted()) {
                 index = 0;
             } else if (mPublicStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0) {
                 index = 1;
@@ -7474,18 +7690,16 @@
             AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
         }
 
-        public int getIndex(int device) {
-            synchronized (VolumeGroupState.class) {
-                int index = mIndexMap.get(device, -1);
-                // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
-                return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
-            }
+        @GuardedBy("AudioService.VolumeStreamState.class")
+        private int getIndex(int device) {
+            int index = mIndexMap.get(device, -1);
+            // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+            return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
         }
 
-        public boolean hasIndexForDevice(int device) {
-            synchronized (VolumeGroupState.class) {
-                return (mIndexMap.get(device, -1) != -1);
-            }
+        @GuardedBy("AudioService.VolumeStreamState.class")
+        private boolean hasIndexForDevice(int device) {
+            return (mIndexMap.get(device, -1) != -1);
         }
 
         public int getMaxIndex() {
@@ -7496,55 +7710,108 @@
             return mIndexMin;
         }
 
-        private boolean isValidLegacyStreamType() {
-            return (mLegacyStreamType != AudioSystem.STREAM_DEFAULT)
-                    && (mLegacyStreamType < mStreamStates.length);
+        private boolean isValidStream(int stream) {
+            return (stream != AudioSystem.STREAM_DEFAULT) && (stream < mStreamStates.length);
         }
 
-        public void applyAllVolumes() {
-            synchronized (VolumeGroupState.class) {
-                int deviceForStream = AudioSystem.DEVICE_NONE;
-                int volumeIndexForStream = 0;
-                if (isValidLegacyStreamType()) {
-                    // Prevent to apply settings twice when group is associated to public stream
-                    deviceForStream = getDeviceForStream(mLegacyStreamType);
-                    volumeIndexForStream = getStreamVolume(mLegacyStreamType);
-                }
+        public boolean isMusic() {
+            return mHasValidStreamType && mPublicStreamType == AudioSystem.STREAM_MUSIC;
+        }
+
+        public void applyAllVolumes(boolean userSwitch) {
+            String caller = "from vgs";
+            synchronized (VolumeStreamState.class) {
                 // apply device specific volumes first
-                int index;
                 for (int i = 0; i < mIndexMap.size(); i++) {
-                    final int device = mIndexMap.keyAt(i);
+                    int device = mIndexMap.keyAt(i);
+                    int index = mIndexMap.valueAt(i);
+                    boolean synced = false;
                     if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
-                        index = mIndexMap.valueAt(i);
-                        if (device == deviceForStream && volumeIndexForStream == index) {
-                            continue;
+                        for (int stream : getLegacyStreamTypes()) {
+                            if (isValidStream(stream)) {
+                                boolean streamMuted = mStreamStates[stream].mIsMuted;
+                                int deviceForStream = getDeviceForStream(stream);
+                                int indexForStream =
+                                        (mStreamStates[stream].getIndex(deviceForStream) + 5) / 10;
+                                if (device == deviceForStream) {
+                                    if (indexForStream == index && (isMuted() == streamMuted)
+                                            && isVssMuteBijective(stream)) {
+                                        synced = true;
+                                        continue;
+                                    }
+                                    if (indexForStream != index) {
+                                        mStreamStates[stream].setIndex(index * 10, device, caller,
+                                                true /*hasModifyAudioSettings*/);
+                                    }
+                                    if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
+                                        mStreamStates[stream].mute(isMuted());
+                                    }
+                                }
+                            }
                         }
-                        if (DEBUG_VOL) {
-                            Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
-                                    + mAudioVolumeGroup.name() + " and device "
-                                    + AudioSystem.getOutputDeviceName(device));
+                        if (!synced) {
+                            if (DEBUG_VOL) {
+                                Log.d(TAG, "applyAllVolumes: apply index " + index + ", group "
+                                        + mAudioVolumeGroup.name() + " and device "
+                                        + AudioSystem.getOutputDeviceName(device));
+                            }
+                            setVolumeIndexInt(isMuted() ? 0 : index, device, 0 /*flags*/);
                         }
-                        setVolumeIndexInt(index, device, 0 /*flags*/);
                     }
                 }
                 // apply default volume last: by convention , default device volume will be used
                 // by audio policy manager if no explicit volume is present for a given device type
-                index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
-                if (DEBUG_VOL) {
-                    Log.v(TAG, "applyAllVolumes: restore default device index " + index
-                            + " for group " + mAudioVolumeGroup.name());
-                }
-                if (isValidLegacyStreamType()) {
-                    int defaultStreamIndex = (mStreamStates[mLegacyStreamType]
-                            .getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10;
-                    if (defaultStreamIndex == index) {
-                        return;
+                int index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+                boolean synced = false;
+                int deviceForVolume = getDeviceForVolume();
+                boolean forceDeviceSync = userSwitch && (mIndexMap.indexOfKey(deviceForVolume) < 0);
+                for (int stream : getLegacyStreamTypes()) {
+                    if (isValidStream(stream)) {
+                        boolean streamMuted = mStreamStates[stream].mIsMuted;
+                        int defaultStreamIndex = (mStreamStates[stream].getIndex(
+                                        AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10;
+                        if (forceDeviceSync) {
+                            mStreamStates[stream].setIndex(index * 10, deviceForVolume, caller,
+                                    true /*hasModifyAudioSettings*/);
+                        }
+                        if (defaultStreamIndex == index && (isMuted() == streamMuted)
+                                && isVssMuteBijective(stream)) {
+                            synced = true;
+                            continue;
+                        }
+                        if (defaultStreamIndex != index) {
+                            mStreamStates[stream].setIndex(
+                                    index * 10, AudioSystem.DEVICE_OUT_DEFAULT, caller,
+                                    true /*hasModifyAudioSettings*/);
+                        }
+                        if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
+                            mStreamStates[stream].mute(isMuted());
+                        }
                     }
                 }
-                setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+                if (!synced) {
+                    if (DEBUG_VOL) {
+                        Log.d(TAG, "applyAllVolumes: apply default device index " + index
+                                + ", group " + mAudioVolumeGroup.name());
+                    }
+                    setVolumeIndexInt(
+                            isMuted() ? 0 : index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+                }
+                if (forceDeviceSync) {
+                    if (DEBUG_VOL) {
+                        Log.d(TAG, "applyAllVolumes: forceDeviceSync index " + index
+                                + ", device " + AudioSystem.getOutputDeviceName(deviceForVolume)
+                                + ", group " + mAudioVolumeGroup.name());
+                    }
+                    setVolumeIndexInt(isMuted() ? 0 : index, deviceForVolume, 0);
+                }
             }
         }
 
+        public void clearIndexCache() {
+            mIndexMap.clear();
+        }
+
         private void persistVolumeGroup(int device) {
             if (mUseFixedVolume) {
                 return;
@@ -7553,21 +7820,19 @@
                 Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
                         + mAudioVolumeGroup.name()
                         + ", device " + AudioSystem.getOutputDeviceName(device)
-                        + " and User=" + ActivityManager.getCurrentUser());
+                        + " and User=" + getCurrentUserId());
             }
             boolean success = mSettings.putSystemIntForUser(mContentResolver,
                     getSettingNameForDevice(device),
                     getIndex(device),
-                    UserHandle.USER_CURRENT);
+                    isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
             if (!success) {
                 Log.e(TAG, "persistVolumeGroup failed for group " +  mAudioVolumeGroup.name());
             }
         }
 
         public void readSettings() {
-            synchronized (VolumeGroupState.class) {
-                // First clear previously loaded (previous user?) settings
-                mIndexMap.clear();
+            synchronized (VolumeStreamState.class) {
                 // force maximum volume on all streams if fixed volume property is set
                 if (mUseFixedVolume) {
                     mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
@@ -7582,7 +7847,8 @@
                     int index;
                     String name = getSettingNameForDevice(device);
                     index = mSettings.getSystemIntForUser(
-                            mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+                            mContentResolver, name, defaultIndex,
+                            isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
                     if (index == -1) {
                         continue;
                     }
@@ -7593,13 +7859,14 @@
                     if (DEBUG_VOL) {
                         Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
                                  + " for group " + mAudioVolumeGroup.name() + ", device: " + name
-                                 + ", User=" + ActivityManager.getCurrentUser());
+                                 + ", User=" + getCurrentUserId());
                     }
                     mIndexMap.put(device, getValidIndex(index));
                 }
             }
         }
 
+        @GuardedBy("AudioService.VolumeStreamState.class")
         private int getValidIndex(int index) {
             if (index < mIndexMin) {
                 return mIndexMin;
@@ -7610,15 +7877,17 @@
         }
 
         public @NonNull String getSettingNameForDevice(int device) {
-            final String suffix = AudioSystem.getOutputDeviceName(device);
+            String suffix = AudioSystem.getOutputDeviceName(device);
             if (suffix.isEmpty()) {
-                return mAudioVolumeGroup.name();
+                return mSettingName;
             }
-            return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
+            return mSettingName + "_" + AudioSystem.getOutputDeviceName(device);
         }
 
         private void dump(PrintWriter pw) {
             pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
+            pw.print("   Muted: ");
+            pw.println(mIsMuted);
             pw.print("   Min: ");
             pw.println(mIndexMin);
             pw.print("   Max: ");
@@ -7628,9 +7897,9 @@
                 if (i > 0) {
                     pw.print(", ");
                 }
-                final int device = mIndexMap.keyAt(i);
+                int device = mIndexMap.keyAt(i);
                 pw.print(Integer.toHexString(device));
-                final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+                String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
                         : AudioSystem.getOutputDeviceName(device);
                 if (!deviceName.isEmpty()) {
                     pw.print(" (");
@@ -7643,7 +7912,7 @@
             pw.println();
             pw.print("   Devices: ");
             int n = 0;
-            final int devices = getDeviceForVolume();
+            int devices = getDeviceForVolume();
             for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
                 if ((devices & device) == device) {
                     if (n++ > 0) {
@@ -7652,6 +7921,10 @@
                     pw.print(AudioSystem.getOutputDeviceName(device));
                 }
             }
+            pw.println();
+            pw.print("   Streams: ");
+            Arrays.stream(getLegacyStreamTypes())
+                    .forEach(stream -> pw.print(AudioSystem.streamToString(stream) + " "));
         }
     }
 
@@ -7663,13 +7936,14 @@
     //  4       VolumeStreamState.class
     private class VolumeStreamState {
         private final int mStreamType;
+        private VolumeGroupState mVolumeGroupState = null;
         private int mIndexMin;
         // min index when user doesn't have permission to change audio settings
         private int mIndexMinNoPerm;
         private int mIndexMax;
 
-        private boolean mIsMuted;
-        private boolean mIsMutedInternally;
+        private boolean mIsMuted = false;
+        private boolean mIsMutedInternally = false;
         private String mVolumeIndexSettingName;
         @NonNull private Set<Integer> mObservedDeviceSet = new TreeSet<>();
 
@@ -7727,6 +8001,15 @@
         }
 
         /**
+         * Associate a {@link volumeGroupState} on the {@link VolumeStreamState}.
+         * <p> It helps to synchronize the index, mute attributes on the maching
+         * {@link volumeGroupState}
+         * @param volumeGroupState matching the {@link VolumeStreamState}
+         */
+        public void setVolumeGroupState(VolumeGroupState volumeGroupState) {
+            mVolumeGroupState = volumeGroupState;
+        }
+        /**
          * Update the minimum index that can be used without MODIFY_AUDIO_SETTINGS permission
          * @param index minimum index expressed in "UI units", i.e. no 10x factor
          */
@@ -7984,6 +8267,9 @@
                 }
             }
             if (changed) {
+                // If associated to volume group, update group cache
+                updateVolumeGroupIndex(device, /* forceMuteState= */ false);
+
                 oldIndex = (oldIndex + 5) / 10;
                 index = (index + 5) / 10;
                 // log base stream changes to the event log
@@ -8086,6 +8372,29 @@
             }
         }
 
+        // If associated to volume group, update group cache
+        private void updateVolumeGroupIndex(int device, boolean forceMuteState) {
+            synchronized (VolumeStreamState.class) {
+                if (mVolumeGroupState != null) {
+                    int groupIndex = (getIndex(device) + 5) / 10;
+                    if (DEBUG_VOL) {
+                        Log.d(TAG, "updateVolumeGroupIndex for stream " + mStreamType
+                                + ", muted=" + mIsMuted + ", device=" + device + ", index="
+                                + getIndex(device) + ", group " + mVolumeGroupState.name()
+                                + " Muted=" + mVolumeGroupState.isMuted() + ", Index=" + groupIndex
+                                + ", forceMuteState=" + forceMuteState);
+                    }
+                    mVolumeGroupState.updateVolumeIndex(groupIndex, device);
+                    // Only propage mute of stream when applicable
+                    if (isMutable()) {
+                        // For call stream, align mute only when muted, not when index is set to 0
+                        mVolumeGroupState.mute(
+                                forceMuteState ? mIsMuted : groupIndex == 0 || mIsMuted);
+                    }
+                }
+            }
+        }
+
         /**
          * Mute/unmute the stream
          * @param state the new mute state
@@ -8094,27 +8403,10 @@
         public boolean mute(boolean state) {
             boolean changed = false;
             synchronized (VolumeStreamState.class) {
-                if (state != mIsMuted) {
-                    changed = true;
-                    mIsMuted = state;
-
-                    // Set the new mute volume. This propagates the values to
-                    // the audio system, otherwise the volume won't be changed
-                    // at the lower level.
-                    sendMsg(mAudioHandler,
-                            MSG_SET_ALL_VOLUMES,
-                            SENDMSG_QUEUE,
-                            0,
-                            0,
-                            this, 0);
-                }
+                changed = mute(state, true);
             }
             if (changed) {
-                // Stream mute changed, fire the intent.
-                Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
-                intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
-                intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state);
-                sendBroadcastToAll(intent);
+                broadcastMuteSetting(mStreamType, state);
             }
             return changed;
         }
@@ -8146,6 +8438,50 @@
             return mIsMuted || mIsMutedInternally;
         }
 
+
+        private boolean isMutable() {
+            return isStreamAffectedByMute(mStreamType)
+                    && (mIndexMin == 0 || isCallStream(mStreamType));
+        }
+
+        /**
+         * Mute/unmute the stream
+         * @param state the new mute state
+         * @param apply true to propagate to HW, or false just to update the cache. May be needed
+         * to mute a stream and its aliases as applyAllVolume will force settings to aliases.
+         * It prevents unnecessary calls to {@see AudioSystem#setStreamVolume}
+         * @return true if the mute state was changed
+         */
+        public boolean mute(boolean state, boolean apply) {
+            synchronized (VolumeStreamState.class) {
+                boolean changed = state != mIsMuted;
+                if (changed) {
+                    mIsMuted = state;
+                    if (apply) {
+                        doMute();
+                    }
+                }
+                return changed;
+            }
+        }
+
+        public void doMute() {
+            synchronized (VolumeStreamState.class) {
+                // If associated to volume group, update group cache
+                updateVolumeGroupIndex(getDeviceForStream(mStreamType), /* forceMuteState= */ true);
+
+                // Set the new mute volume. This propagates the values to
+                // the audio system, otherwise the volume won't be changed
+                // at the lower level.
+                sendMsg(mAudioHandler,
+                        MSG_SET_ALL_VOLUMES,
+                        SENDMSG_QUEUE,
+                        0,
+                        0,
+                        this, 0);
+            }
+        }
+
         public int getStreamType() {
             return mStreamType;
         }
@@ -8215,6 +8551,9 @@
             pw.println();
             pw.print("   Devices: ");
             pw.print(AudioSystem.deviceSetToString(getDeviceSetForStream(mStreamType)));
+            pw.println();
+            pw.print("   Volume Group: ");
+            pw.println(mVolumeGroupState != null ? mVolumeGroupState.name() : "n/a");
         }
     }
 
@@ -9881,7 +10220,7 @@
     private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
 
     private static final String ACTION_CHECK_MUSIC_ACTIVE =
-            AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+            "com.android.server.audio.action.CHECK_MUSIC_ACTIVE";
     private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
 
     private int safeMediaVolumeIndex(int device) {
@@ -9957,7 +10296,8 @@
                                   mPendingVolumeCommand.mIndex,
                                   mPendingVolumeCommand.mFlags,
                                   mPendingVolumeCommand.mDevice,
-                                  callingPackage, true /*hasModifyAudioSettings*/);
+                                  callingPackage, true /*hasModifyAudioSettings*/,
+                                  true /*canChangeMute*/);
                 mPendingVolumeCommand = null;
             }
         }
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index c2c3f02..6cbe03e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -17,7 +17,6 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
-import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioManager;
 import android.media.AudioSystem;
@@ -221,6 +220,7 @@
         static final int VOL_SET_GROUP_VOL = 8;
         static final int VOL_MUTE_STREAM_INT = 9;
         static final int VOL_SET_LE_AUDIO_VOL = 10;
+        static final int VOL_ADJUST_GROUP_VOL = 11;
 
         final int mOp;
         final int mStream;
@@ -228,7 +228,6 @@
         final int mVal2;
         final String mCaller;
         final String mGroupName;
-        final AudioAttributes mAudioAttributes;
 
         /** used for VOL_ADJUST_VOL_UID,
          *           VOL_ADJUST_SUGG_VOL,
@@ -241,7 +240,6 @@
             mVal2 = val2;
             mCaller = caller;
             mGroupName = null;
-            mAudioAttributes = null;
             logMetricEvent();
         }
 
@@ -254,7 +252,6 @@
             mStream = -1;
             mCaller = null;
             mGroupName = null;
-            mAudioAttributes = null;
             logMetricEvent();
         }
 
@@ -267,7 +264,6 @@
             mStream = -1;
             mCaller = null;
             mGroupName = null;
-            mAudioAttributes = null;
             logMetricEvent();
         }
 
@@ -280,7 +276,6 @@
             // unused
             mCaller = null;
             mGroupName = null;
-            mAudioAttributes = null;
             logMetricEvent();
         }
 
@@ -293,19 +288,18 @@
             // unused
             mCaller = null;
             mGroupName = null;
-            mAudioAttributes = null;
             logMetricEvent();
         }
 
-        /** used for VOL_SET_GROUP_VOL */
-        VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) {
+        /** used for VOL_SET_GROUP_VOL,
+         *           VOL_ADJUST_GROUP_VOL */
+        VolumeEvent(int op, String group, int index, int flags, String caller) {
             mOp = op;
             mStream = -1;
             mVal1 = index;
             mVal2 = flags;
             mCaller = caller;
             mGroupName = group;
-            mAudioAttributes = aa;
             logMetricEvent();
         }
 
@@ -317,7 +311,6 @@
             mVal2 = 0;
             mCaller = null;
             mGroupName = null;
-            mAudioAttributes = null;
             logMetricEvent();
         }
 
@@ -359,6 +352,15 @@
                             .record();
                     return;
                 }
+                case VOL_ADJUST_GROUP_VOL:
+                    new MediaMetrics.Item(mMetricsId)
+                            .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
+                            .set(MediaMetrics.Property.DIRECTION, mVal1 > 0 ? "up" : "down")
+                            .set(MediaMetrics.Property.EVENT, "adjustVolumeGroupVolume")
+                            .set(MediaMetrics.Property.FLAGS, mVal2)
+                            .set(MediaMetrics.Property.GROUP, mGroupName)
+                            .record();
+                    return;
                 case VOL_SET_STREAM_VOL:
                     new MediaMetrics.Item(mMetricsId)
                             .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
@@ -410,7 +412,6 @@
                     return;
                 case VOL_SET_GROUP_VOL:
                     new MediaMetrics.Item(mMetricsId)
-                            .set(MediaMetrics.Property.ATTRIBUTES, mAudioAttributes.toString())
                             .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
                             .set(MediaMetrics.Property.EVENT, "setVolumeIndexForAttributes")
                             .set(MediaMetrics.Property.FLAGS, mVal2)
@@ -436,6 +437,13 @@
                             .append(" flags:0x").append(Integer.toHexString(mVal2))
                             .append(") from ").append(mCaller)
                             .toString();
+                case VOL_ADJUST_GROUP_VOL:
+                    return new StringBuilder("adjustVolumeGroupVolume(group:")
+                            .append(mGroupName)
+                            .append(" dir:").append(AudioManager.adjustToString(mVal1))
+                            .append(" flags:0x").append(Integer.toHexString(mVal2))
+                            .append(") from ").append(mCaller)
+                            .toString();
                 case VOL_ADJUST_STREAM_VOL:
                     return new StringBuilder("adjustStreamVolume(stream:")
                             .append(AudioSystem.streamToString(mStream))
@@ -484,8 +492,7 @@
                             .append(" stream:").append(AudioSystem.streamToString(mStream))
                             .toString();
                 case VOL_SET_GROUP_VOL:
-                    return new StringBuilder("setVolumeIndexForAttributes(attr:")
-                            .append(mAudioAttributes.toString())
+                    return new StringBuilder("setVolumeIndexForAttributes(group:")
                             .append(" group: ").append(mGroupName)
                             .append(" index:").append(mVal1)
                             .append(" flags:0x").append(Integer.toHexString(mVal2))
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 2588371..a17b4bf 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -29,8 +29,12 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -60,8 +64,12 @@
     private String[] mMethodNames = {"getDevicesForAttributes"};
 
     private static final boolean USE_CACHE_FOR_GETDEVICES = true;
+    private static final Object sDeviceCacheLock = new Object();
+    @GuardedBy("sDeviceCacheLock")
     private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
             mDevicesForAttrCache;
+    @GuardedBy("sDeviceCacheLock")
+    private long mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
     private int[] mMethodCacheHit;
     private static final Object sRoutingListenerLock = new Object();
     @GuardedBy("sRoutingListenerLock")
@@ -147,9 +155,11 @@
             AudioSystem.setRoutingCallback(sSingletonDefaultAdapter);
             AudioSystem.setVolumeRangeInitRequestCallback(sSingletonDefaultAdapter);
             if (USE_CACHE_FOR_GETDEVICES) {
-                sSingletonDefaultAdapter.mDevicesForAttrCache =
-                        new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
-                sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
+                synchronized (sDeviceCacheLock) {
+                    sSingletonDefaultAdapter.mDevicesForAttrCache =
+                            new ConcurrentHashMap<>(AudioSystem.getNumStreamTypes());
+                    sSingletonDefaultAdapter.mMethodCacheHit = new int[NB_MEASUREMENTS];
+                }
             }
             if (ENABLE_GETDEVICES_STATS) {
                 sSingletonDefaultAdapter.mMethodCallCounter = new int[NB_MEASUREMENTS];
@@ -163,8 +173,9 @@
         if (DEBUG_CACHE) {
             Log.d(TAG, "---- clearing cache ----------");
         }
-        if (mDevicesForAttrCache != null) {
-            synchronized (mDevicesForAttrCache) {
+        synchronized (sDeviceCacheLock) {
+            if (mDevicesForAttrCache != null) {
+                mDevicesForAttributesCacheClearTimeMs = System.currentTimeMillis();
                 mDevicesForAttrCache.clear();
             }
         }
@@ -193,7 +204,7 @@
         if (USE_CACHE_FOR_GETDEVICES) {
             ArrayList<AudioDeviceAttributes> res;
             final Pair<AudioAttributes, Boolean> key = new Pair(attributes, forVolume);
-            synchronized (mDevicesForAttrCache) {
+            synchronized (sDeviceCacheLock) {
                 res = mDevicesForAttrCache.get(key);
                 if (res == null) {
                     // result from AudioSystem guaranteed non-null, but could be invalid
@@ -508,23 +519,31 @@
      */
     public void dump(PrintWriter pw) {
         pw.println("\nAudioSystemAdapter:");
-        pw.println(" mDevicesForAttrCache:");
-        if (mDevicesForAttrCache != null) {
-            for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
-                    entry : mDevicesForAttrCache.entrySet()) {
-                final AudioAttributes attributes = entry.getKey().first;
-                try {
-                    final int stream = attributes.getVolumeControlStream();
-                    pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
-                            + " stream: "
-                            + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
-                    for (AudioDeviceAttributes devAttr : entry.getValue()) {
-                        pw.println("\t\t" + devAttr);
+        final DateTimeFormatter formatter = DateTimeFormatter
+                .ofPattern("MM-dd HH:mm:ss:SSS")
+                .withLocale(Locale.US)
+                .withZone(ZoneId.systemDefault());
+        synchronized (sDeviceCacheLock) {
+            pw.println(" last cache clear time: " + formatter.format(
+                    Instant.ofEpochMilli(mDevicesForAttributesCacheClearTimeMs)));
+            pw.println(" mDevicesForAttrCache:");
+            if (mDevicesForAttrCache != null) {
+                for (Map.Entry<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
+                        entry : mDevicesForAttrCache.entrySet()) {
+                    final AudioAttributes attributes = entry.getKey().first;
+                    try {
+                        final int stream = attributes.getVolumeControlStream();
+                        pw.println("\t" + attributes + " forVolume: " + entry.getKey().second
+                                + " stream: "
+                                + AudioSystem.STREAM_NAMES[stream] + "(" + stream + ")");
+                        for (AudioDeviceAttributes devAttr : entry.getValue()) {
+                            pw.println("\t\t" + devAttr);
+                        }
+                    } catch (IllegalArgumentException e) {
+                        // dump could fail if attributes do not map to a stream.
+                        pw.println("\t dump failed for attributes: " + attributes);
+                        Log.e(TAG, "dump failed", e);
                     }
-                } catch (IllegalArgumentException e) {
-                    // dump could fail if attributes do not map to a stream.
-                    pw.println("\t dump failed for attributes: " + attributes);
-                    Log.e(TAG, "dump failed", e);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5cfe65b..4341634 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -258,7 +258,7 @@
             mDisplayWindowPolicyControllers = new SparseArray<>();
 
     /**
-     *  Map of every internal primary display device {@link HighBrightnessModeMetadata}s indexed by
+     *  Map of every display device {@link HighBrightnessModeMetadata}s indexed by
      *  {@link DisplayDevice#mUniqueId}.
      */
     public final ArrayMap<String, HighBrightnessModeMetadata> mHighBrightnessModeMetadataMap =
@@ -1525,6 +1525,7 @@
         final int displayId = display.getDisplayIdLocked();
         final boolean isDefault = displayId == Display.DEFAULT_DISPLAY;
         configureColorModeLocked(display, device);
+
         if (!mAreUserDisabledHdrTypesAllowed) {
             display.setUserDisabledHdrTypes(mUserDisabledHdrTypes);
         }
@@ -2636,7 +2637,8 @@
         mLogicalDisplayMapper.forEachLocked(this::addDisplayPowerControllerLocked);
     }
 
-    private HighBrightnessModeMetadata getHighBrightnessModeMetadata(LogicalDisplay display) {
+    @VisibleForTesting
+    HighBrightnessModeMetadata getHighBrightnessModeMetadata(LogicalDisplay display) {
         final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
         if (device == null) {
             Slog.wtf(TAG, "Display Device is null in DisplayPowerController for display: "
@@ -2644,11 +2646,6 @@
             return null;
         }
 
-        // HBM brightness mode is only applicable to internal physical displays.
-        if (display.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
-            return null;
-        }
-
         final String uniqueId = device.getUniqueId();
 
         if (mHighBrightnessModeMetadataMap.containsKey(uniqueId)) {
@@ -2673,7 +2670,7 @@
         final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
                 display, mSyncRoot);
 
-        // If display is internal and has a HighBrightnessModeMetadata mapping, use that.
+        // If display already has a HighBrightnessModeMetadata mapping, use that.
         // Or create a new one and use that.
         // We also need to pass a mapping of the HighBrightnessModeTimeInfoMap to
         // displayPowerController, so the hbm info can be correctly associated
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index fdfc20a..7c6b667 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -561,7 +561,7 @@
 
     /**
      * Sets the display mode switching type.
-     * @param newType
+     * @param newType new mode switching type
      */
     public void setModeSwitchingType(@DisplayManager.SwitchingType int newType) {
         synchronized (mLock) {
@@ -678,6 +678,18 @@
         notifyDesiredDisplayModeSpecsChangedLocked();
     }
 
+    @GuardedBy("mLock")
+    private float getMaxRefreshRateLocked(int displayId) {
+        Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
+        float maxRefreshRate = 0f;
+        for (Display.Mode mode : modes) {
+            if (mode.getRefreshRate() > maxRefreshRate) {
+                maxRefreshRate = mode.getRefreshRate();
+            }
+        }
+        return maxRefreshRate;
+    }
+
     private void notifyDesiredDisplayModeSpecsChangedLocked() {
         if (mDesiredDisplayModeSpecsListener != null
                 && !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) {
@@ -996,25 +1008,29 @@
         // of low priority voters. It votes [0, max(PEAK, MIN)]
         public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 7;
 
+        // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
+        // rate to max value (same as for PRIORITY_UDFPS) on lock screen
+        public static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;
+
         // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
-        public static final int PRIORITY_LOW_POWER_MODE = 8;
+        public static final int PRIORITY_LOW_POWER_MODE = 9;
 
         // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
         // higher priority voters' result is a range, it will fix the rate to a single choice.
         // It's used to avoid refresh rate switches in certain conditions which may result in the
         // user seeing the display flickering when the switches occur.
-        public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 9;
+        public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 10;
 
         // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
-        public static final int PRIORITY_SKIN_TEMPERATURE = 10;
+        public static final int PRIORITY_SKIN_TEMPERATURE = 11;
 
         // The proximity sensor needs the refresh rate to be locked in order to function, so this is
         // set to a high priority.
-        public static final int PRIORITY_PROXIMITY = 11;
+        public static final int PRIORITY_PROXIMITY = 12;
 
         // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
         // to function, so this needs to be the highest priority of all votes.
-        public static final int PRIORITY_UDFPS = 12;
+        public static final int PRIORITY_UDFPS = 13;
 
         // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
         // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -1117,6 +1133,8 @@
                     return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE";
                 case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
                     return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE";
+                case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
+                    return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
                 default:
                     return Integer.toString(priority);
             }
@@ -2329,6 +2347,7 @@
 
     private class UdfpsObserver extends IUdfpsHbmListener.Stub {
         private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray();
+        private final SparseBooleanArray mAuthenticationPossible = new SparseBooleanArray();
 
         public void observe() {
             StatusBarManagerInternal statusBar =
@@ -2354,25 +2373,28 @@
 
         private void updateHbmStateLocked(int displayId, boolean enabled) {
             mLocalHbmEnabled.put(displayId, enabled);
-            updateVoteLocked(displayId);
+            updateVoteLocked(displayId, enabled, Vote.PRIORITY_UDFPS);
         }
 
-        private void updateVoteLocked(int displayId) {
+        @Override
+        public void onAuthenticationPossible(int displayId, boolean isPossible) {
+            synchronized (mLock) {
+                mAuthenticationPossible.put(displayId, isPossible);
+                updateVoteLocked(displayId, isPossible,
+                        Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+            }
+        }
+
+        @GuardedBy("mLock")
+        private void updateVoteLocked(int displayId, boolean enabled, int votePriority) {
             final Vote vote;
-            if (mLocalHbmEnabled.get(displayId)) {
-                Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
-                float maxRefreshRate = 0f;
-                for (Display.Mode mode : modes) {
-                    if (mode.getRefreshRate() > maxRefreshRate) {
-                        maxRefreshRate = mode.getRefreshRate();
-                    }
-                }
+            if (enabled) {
+                float maxRefreshRate = DisplayModeDirector.this.getMaxRefreshRateLocked(displayId);
                 vote = Vote.forRefreshRates(maxRefreshRate, maxRefreshRate);
             } else {
                 vote = null;
             }
-
-            DisplayModeDirector.this.updateVoteLocked(displayId, Vote.PRIORITY_UDFPS, vote);
+            DisplayModeDirector.this.updateVoteLocked(displayId, votePriority, vote);
         }
 
         void dumpLocked(PrintWriter pw) {
@@ -2383,6 +2405,13 @@
                 final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
                 pw.println("      Display " + displayId + ": " + enabled);
             }
+            pw.println("    mAuthenticationPossible: ");
+            for (int i = 0; i < mAuthenticationPossible.size(); i++) {
+                final int displayId = mAuthenticationPossible.keyAt(i);
+                final String isPossible = mAuthenticationPossible.valueAt(i) ? "possible"
+                        : "impossible";
+                pw.println("      Display " + displayId + ": " + isPossible);
+            }
         }
     }
 
@@ -2812,8 +2841,8 @@
                     .sendToTarget();
 
             if (refreshRateInLowZone != -1) {
-                mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone)
-                    .sendToTarget();
+                mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone,
+                        0).sendToTarget();
             }
 
             int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds();
@@ -2825,8 +2854,8 @@
                     .sendToTarget();
 
             if (refreshRateInHighZone != -1) {
-                mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone)
-                    .sendToTarget();
+                mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone,
+                        0).sendToTarget();
             }
 
             final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight();
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java
index 37234ff..8aa3631 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeMetadata.java
@@ -21,10 +21,9 @@
 
 /**
  * Represents High Brightness Mode metadata associated
- * with a specific internal physical display.
+ * with a specific display.
  * Required for separately storing data like time information,
- * and related events when display was in HBM mode per
- * physical internal display.
+ * and related events when display was in HBM mode per display.
  */
 class HighBrightnessModeMetadata {
     /**
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 375e51c..1f7232a 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -159,6 +159,7 @@
     private Layout mCurrentLayout = null;
     private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
     private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+    private int mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
     private boolean mBootCompleted = false;
     private boolean mInteractive;
 
@@ -353,6 +354,12 @@
         ipw.println("mDeviceStatesOnWhichToWakeUp=" + mDeviceStatesOnWhichToWakeUp);
         ipw.println("mDeviceStatesOnWhichToSleep=" + mDeviceStatesOnWhichToSleep);
         ipw.println("mInteractive=" + mInteractive);
+        ipw.println("mBootCompleted=" + mBootCompleted);
+
+        ipw.println();
+        ipw.println("mDeviceState=" + mDeviceState);
+        ipw.println("mPendingDeviceState=" + mPendingDeviceState);
+        ipw.println("mDeviceStateToBeAppliedAfterBoot=" + mDeviceStateToBeAppliedAfterBoot);
 
         final int logicalDisplayCount = mLogicalDisplays.size();
         ipw.println();
@@ -370,10 +377,6 @@
     }
 
     void setDeviceStateLocked(int state, boolean isOverrideActive) {
-        Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
-                + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
-        mPendingDeviceState = state;
-
         if (!mBootCompleted) {
             // The boot animation might still be in progress, we do not want to switch states now
             // as the boot animation would end up with an incorrect size.
@@ -381,14 +384,19 @@
                 Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState
                         + " until boot is completed");
             }
+            mDeviceStateToBeAppliedAfterBoot = state;
             return;
         }
 
+        Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
+                + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
         // As part of a state transition, we may need to turn off some displays temporarily so that
         // the transition is smooth. Plus, on some devices, only one internal displays can be
         // on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be
         // temporarily turned off.
         resetLayoutLocked(mDeviceState, state, /* isStateChangeStarting= */ true);
+        mPendingDeviceState = state;
+        mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
         final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
                 mInteractive, mBootCompleted);
         final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
@@ -435,8 +443,9 @@
     void onBootCompleted() {
         synchronized (mSyncRoot) {
             mBootCompleted = true;
-            if (mPendingDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
-                setDeviceStateLocked(mPendingDeviceState, /* isOverrideActive= */ false);
+            if (mDeviceStateToBeAppliedAfterBoot != DeviceStateManager.INVALID_DEVICE_STATE) {
+                setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot,
+                        /* isOverrideActive= */ false);
             }
         }
     }
@@ -491,7 +500,8 @@
     @VisibleForTesting
     boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive,
             boolean isInteractive, boolean isBootCompleted) {
-        return mDeviceStatesOnWhichToSleep.get(pendingState)
+        return currentState != DeviceStateManager.INVALID_DEVICE_STATE
+                && mDeviceStatesOnWhichToSleep.get(pendingState)
                 && !mDeviceStatesOnWhichToSleep.get(currentState)
                 && !isOverrideActive
                 && isInteractive && isBootCompleted;
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 017b96c..2276715 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -107,11 +107,6 @@
         Matrix.setIdentityM(MATRIX_IDENTITY, 0);
     }
 
-    /**
-     * The transition time, in milliseconds, for Night Display to turn on/off.
-     */
-    private static final long TRANSITION_DURATION = 3000L;
-
     private static final int MSG_USER_CHANGED = 0;
     private static final int MSG_SET_UP = 1;
     private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 2;
@@ -661,7 +656,7 @@
             TintValueAnimator valueAnimator = TintValueAnimator.ofMatrix(COLOR_MATRIX_EVALUATOR,
                     from == null ? MATRIX_IDENTITY : from, to);
             tintController.setAnimator(valueAnimator);
-            valueAnimator.setDuration(TRANSITION_DURATION);
+            valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds());
             valueAnimator.setInterpolator(AnimationUtils.loadInterpolator(
                     getContext(), android.R.interpolator.fast_out_slow_in));
             valueAnimator.addUpdateListener((ValueAnimator animator) -> {
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index 93a78c1..f27ccc7 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -59,7 +59,8 @@
     private float[] mCurrentColorTemperatureXYZ;
     @VisibleForTesting
     boolean mSetUp = false;
-    private float[] mMatrixDisplayWhiteBalance = new float[16];
+    private final float[] mMatrixDisplayWhiteBalance = new float[16];
+    private long mTransitionDuration;
     private Boolean mIsAvailable;
     // This feature becomes disallowed if the device is in an unsupported strong/light state.
     private boolean mIsAllowed = true;
@@ -119,6 +120,9 @@
         final int colorTemperature = res.getInteger(
                 R.integer.config_displayWhiteBalanceColorTemperatureDefault);
 
+        mTransitionDuration = res.getInteger(
+                R.integer.config_displayWhiteBalanceTransitionTime);
+
         synchronized (mLock) {
             mDisplayColorSpaceRGB = displayColorSpaceRGB;
             mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
@@ -232,6 +236,11 @@
     }
 
     @Override
+    public long getTransitionDurationMilliseconds() {
+        return mTransitionDuration;
+    }
+
+    @Override
     public void dump(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("    mSetUp = " + mSetUp);
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index 422dd32..c53ac06 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -16,7 +16,6 @@
 
 package com.android.server.display.color;
 
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.util.Slog;
 
@@ -24,6 +23,11 @@
 
 abstract class TintController {
 
+    /**
+     * The default transition time, in milliseconds, for color transforms to turn on/off.
+     */
+    private static final long TRANSITION_DURATION = 3000L;
+
     private ColorDisplayService.TintValueAnimator mAnimator;
     private Boolean mIsActivated;
 
@@ -66,6 +70,10 @@
         return mIsActivated == null;
     }
 
+    public long getTransitionDurationMilliseconds() {
+        return TRANSITION_DURATION;
+    }
+
     /**
      * Dump debug information.
      */
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 85d5b4f..5b772fc 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -419,16 +419,16 @@
         float ambientBrightness = mBrightnessFilter.getEstimate(time);
         mLatestAmbientBrightness = ambientBrightness;
 
-        if (ambientColorTemperature != -1.0f &&
-                mLowLightAmbientBrightnessToBiasSpline != null) {
+        if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
+                && mLowLightAmbientBrightnessToBiasSpline != null) {
             float bias = mLowLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
             ambientColorTemperature =
                     bias * ambientColorTemperature + (1.0f - bias)
                     * mLowLightAmbientColorTemperature;
             mLatestLowLightBias = bias;
         }
-        if (ambientColorTemperature != -1.0f &&
-                mHighLightAmbientBrightnessToBiasSpline != null) {
+        if (ambientColorTemperature != -1.0f && ambientBrightness != -1.0f
+                && mHighLightAmbientBrightnessToBiasSpline != null) {
             float bias = mHighLightAmbientBrightnessToBiasSpline.interpolate(ambientBrightness);
             ambientColorTemperature =
                     (1.0f - bias) * ambientColorTemperature + bias
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index db9deb1..f87a146 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -244,7 +244,10 @@
                 }
 
                 mListener.onDreamStopped(dream.mToken);
+            } else if (dream.mCanDoze && !mCurrentDream.mCanDoze) {
+                mListener.stopDozing(dream.mToken);
             }
+
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
@@ -289,6 +292,7 @@
      */
     public interface Listener {
         void onDreamStopped(Binder token);
+        void stopDozing(Binder token);
     }
 
     private final class DreamRecord implements DeathRecipient, ServiceConnection {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index bb1e393..148b80e 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -499,7 +499,12 @@
         }
 
         synchronized (mLock) {
-            if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.isDozing) {
+            if (mCurrentDream == null) {
+                return;
+            }
+
+            final boolean sameDream = mCurrentDream.token == token;
+            if ((sameDream && mCurrentDream.isDozing) || (!sameDream && !mCurrentDream.isDozing)) {
                 mCurrentDream.isDozing = false;
                 mDozeWakeLock.release();
                 mPowerManagerInternal.setDozeOverrideFromDreamManager(
@@ -765,6 +770,11 @@
                 }
             }
         }
+
+        @Override
+        public void stopDozing(Binder token) {
+            stopDozingInternal(token);
+        }
     };
 
     private final ContentObserver mDozeEnabledObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 1235352..f0aff2a 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -300,6 +300,7 @@
         public void deliverOnFlushComplete(int requestCode) throws PendingIntent.CanceledException {
             BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setDontSendToRestrictedApps(true);
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
 
             mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_FLUSH_COMPLETE, requestCode),
                     null, null, null, options.toBundle());
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 4d55d4e..25fefad 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -132,6 +132,7 @@
 
     // contains connections to all connected services, including app services
     // and system services
+    @GuardedBy("mMutex")
     private final ArrayList<ManagedServiceInfo> mServices = new ArrayList<>();
     /**
      * The services that have been bound by us. If the service is also connected, it will also
@@ -150,13 +151,15 @@
             = new ArraySet<>();
     // Just the packages from mEnabledServicesForCurrentProfiles
     private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>();
-    // List of enabled packages that have nevertheless asked not to be run
-    private ArraySet<ComponentName> mSnoozingForCurrentProfiles = new ArraySet<>();
+    // Per user id, list of enabled packages that have nevertheless asked not to be run
+    private final android.util.SparseSetArray<ComponentName> mSnoozing =
+            new android.util.SparseSetArray<>();
 
     // List of approved packages or components (by user, then by primary/secondary) that are
     // allowed to be bound as managed services. A package or component appearing in this list does
     // not mean that we are currently bound to said package/component.
-    protected ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved = new ArrayMap<>();
+    protected final ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved =
+            new ArrayMap<>();
 
     // List of packages or components (by user) that are configured to be enabled/disabled
     // explicitly by the user
@@ -315,6 +318,7 @@
         return changes;
     }
 
+    @GuardedBy("mApproved")
     private boolean clearUserSetFlagLocked(ComponentName component, int userId) {
         String approvedValue = getApprovedValue(component.flattenToString());
         ArraySet<String> userSet = mUserSetServices.get(userId);
@@ -375,8 +379,8 @@
             pw.println("      " + cmpt);
         }
 
-        pw.println("    Live " + getCaption() + "s (" + mServices.size() + "):");
         synchronized (mMutex) {
+            pw.println("    Live " + getCaption() + "s (" + mServices.size() + "):");
             for (ManagedServiceInfo info : mServices) {
                 if (filter != null && !filter.matches(info.component)) continue;
                 pw.println("      " + info.component
@@ -386,10 +390,15 @@
             }
         }
 
-        pw.println("    Snoozed " + getCaption() + "s (" +
-                mSnoozingForCurrentProfiles.size() + "):");
-        for (ComponentName name : mSnoozingForCurrentProfiles) {
-            pw.println("      " + name.flattenToShortString());
+        synchronized (mSnoozing) {
+            pw.println("    Snoozed " + getCaption() + "s ("
+                    + mSnoozing.size() + "):");
+            for (int i = 0; i < mSnoozing.size(); i++) {
+                pw.println("      User: " + mSnoozing.keyAt(i));
+                for (ComponentName name : mSnoozing.valuesAt(i)) {
+                    pw.println("        " + name.flattenToShortString());
+                }
+            }
         }
     }
 
@@ -431,8 +440,16 @@
             }
         }
 
-        for (ComponentName name : mSnoozingForCurrentProfiles) {
-            name.dumpDebug(proto, ManagedServicesProto.SNOOZED);
+        synchronized (mSnoozing) {
+            for (int i = 0; i < mSnoozing.size(); i++) {
+                long token = proto.start(ManagedServicesProto.SNOOZED);
+                proto.write(ManagedServicesProto.SnoozedServices.USER_ID,
+                        mSnoozing.keyAt(i));
+                for (ComponentName name : mSnoozing.valuesAt(i)) {
+                    name.dumpDebug(proto, ManagedServicesProto.SnoozedServices.SNOOZED);
+                }
+                proto.end(token);
+            }
         }
     }
 
@@ -975,6 +992,9 @@
         synchronized (mApproved) {
             mApproved.remove(user);
         }
+        synchronized (mSnoozing) {
+            mSnoozing.remove(user);
+        }
         rebindServices(true, user);
     }
 
@@ -994,10 +1014,12 @@
             return null;
         }
         final IBinder token = service.asBinder();
-        final int N = mServices.size();
-        for (int i = 0; i < N; i++) {
-            final ManagedServiceInfo info = mServices.get(i);
-            if (info.service.asBinder() == token) return info;
+        synchronized (mMutex) {
+            final int nServices = mServices.size();
+            for (int i = 0; i < nServices; i++) {
+                final ManagedServiceInfo info = mServices.get(i);
+                if (info.service.asBinder() == token) return info;
+            }
         }
         return null;
     }
@@ -1066,15 +1088,17 @@
     }
 
     protected void setComponentState(ComponentName component, int userId, boolean enabled) {
-        boolean previous = !mSnoozingForCurrentProfiles.contains(component);
-        if (previous == enabled) {
-            return;
-        }
+        synchronized (mSnoozing) {
+            boolean previous = !mSnoozing.contains(userId, component);
+            if (previous == enabled) {
+                return;
+            }
 
-        if (enabled) {
-            mSnoozingForCurrentProfiles.remove(component);
-        } else {
-            mSnoozingForCurrentProfiles.add(component);
+            if (enabled) {
+                mSnoozing.remove(userId, component);
+            } else {
+                mSnoozing.add(userId, component);
+            }
         }
 
         // State changed
@@ -1287,7 +1311,10 @@
             }
 
             final Set<ComponentName> add = new HashSet<>(userComponents);
-            add.removeAll(mSnoozingForCurrentProfiles);
+            ArraySet<ComponentName> snoozed = mSnoozing.get(userId);
+            if (snoozed != null) {
+                add.removeAll(snoozed);
+            }
 
             componentsToBind.put(userId, add);
 
@@ -1466,10 +1493,12 @@
         }
     }
 
+    @GuardedBy("mMutex")
     private void registerServiceLocked(final ComponentName name, final int userid) {
         registerServiceLocked(name, userid, false /* isSystem */);
     }
 
+    @GuardedBy("mMutex")
     private void registerServiceLocked(final ComponentName name, final int userid,
             final boolean isSystem) {
         if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);
@@ -1600,6 +1629,7 @@
         }
     }
 
+    @GuardedBy("mMutex")
     private void unregisterServiceLocked(ComponentName name, int userid) {
         final int N = mServices.size();
         for (int i = N - 1; i >= 0; i--) {
@@ -1634,6 +1664,7 @@
         return serviceInfo;
     }
 
+    @GuardedBy("mMutex")
     private ManagedServiceInfo removeServiceLocked(int i) {
         final ManagedServiceInfo info = mServices.remove(i);
         onServiceRemovedLocked(info);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6bc8582..b5fceb5 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6687,6 +6687,31 @@
             }
         }
 
+        // Ensure all actions are present
+        if (notification.actions != null) {
+            boolean hasNullActions = false;
+            int nActions = notification.actions.length;
+            for (int i = 0; i < nActions; i++) {
+                if (notification.actions[i] == null) {
+                    hasNullActions = true;
+                    break;
+                }
+            }
+            if (hasNullActions) {
+                ArrayList<Notification.Action> nonNullActions = new ArrayList<>();
+                for (int i = 0; i < nActions; i++) {
+                    if (notification.actions[i] != null) {
+                        nonNullActions.add(notification.actions[i]);
+                    }
+                }
+                if (nonNullActions.size() != 0) {
+                    notification.actions = nonNullActions.toArray(new Notification.Action[0]);
+                } else {
+                    notification.actions = null;
+                }
+            }
+        }
+
         // Ensure CallStyle has all the correct actions
         if (notification.isStyle(Notification.CallStyle.class)) {
             Notification.Builder builder =
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7da5f51..844c22b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2425,10 +2425,10 @@
             // will be null whereas dataOwnerPkg will contain information about the package
             // which was uninstalled while keeping its data.
             AndroidPackage dataOwnerPkg = mPm.mPackages.get(packageName);
+            PackageSetting dataOwnerPs = mPm.mSettings.getPackageLPr(packageName);
             if (dataOwnerPkg  == null) {
-                PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
-                if (ps != null) {
-                    dataOwnerPkg = ps.getPkg();
+                if (dataOwnerPs != null) {
+                    dataOwnerPkg = dataOwnerPs.getPkg();
                 }
             }
 
@@ -2456,6 +2456,7 @@
             if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
                 if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                         dataOwnerPkg.isDebuggable())) {
+                    // Downgrade is not permitted; a lower version of the app will not be allowed
                     try {
                         PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
                     } catch (PackageManagerException e) {
@@ -2464,6 +2465,24 @@
                         return Pair.create(
                                 PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
                     }
+                } else if (dataOwnerPs.isSystem()) {
+                    // Downgrade is permitted, but system apps can't be downgraded below
+                    // the version preloaded onto the system image
+                    final PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(
+                            dataOwnerPs);
+                    if (disabledPs != null) {
+                        dataOwnerPkg = disabledPs.getPkg();
+                    }
+                    try {
+                        PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
+                    } catch (PackageManagerException e) {
+                        String errorMsg = "System app: " + packageName + " cannot be downgraded to"
+                                + " older than its preloaded version on the system image. "
+                                + e.getMessage();
+                        Slog.w(TAG, errorMsg);
+                        return Pair.create(
+                                PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 890c891..632a34e 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -885,7 +885,12 @@
      * available ShareTarget definitions in this package.
      */
     public List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
-            @NonNull IntentFilter filter) {
+            @NonNull final IntentFilter filter) {
+        return getMatchingShareTargets(filter, null);
+    }
+
+    List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
+            @NonNull final IntentFilter filter, @Nullable final String pkgName) {
         synchronized (mLock) {
             final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
             for (int i = 0; i < mShareTargets.size(); i++) {
@@ -909,8 +914,7 @@
             // included in the result
             findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
                     ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION,
-                    mShortcutUser.mService.mContext.getPackageName(),
-                    0, /*getPinnedByAnyLauncher=*/ false);
+                    pkgName, 0, /*getPinnedByAnyLauncher=*/ false);
 
             final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
             for (int i = 0; i < shortcuts.size(); i++) {
@@ -1108,7 +1112,7 @@
 
         // Now prepare to publish manifest shortcuts.
         List<ShortcutInfo> newManifestShortcutList = null;
-        final int shareTargetSize;
+        int shareTargetSize = 0;
         synchronized (mLock) {
             try {
                 shareTargetSize = mShareTargets.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 0b20683..49831d7 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2512,11 +2512,17 @@
         }
         enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
                 "getShareTargets");
+        final ComponentName chooser = injectChooserActivity();
+        final String pkg = (chooser != null
+                && mPackageManagerInternal.getComponentEnabledSetting(chooser,
+                injectBinderCallingUid(), userId) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+                ? chooser.getPackageName() : mContext.getPackageName();
         synchronized (mLock) {
             throwIfUserLockedL(userId);
             final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
             final ShortcutUser user = getUserShortcutsLocked(userId);
-            user.forAllPackages(p -> shortcutInfoList.addAll(p.getMatchingShareTargets(filter)));
+            user.forAllPackages(p -> shortcutInfoList.addAll(
+                    p.getMatchingShareTargets(filter, pkg)));
             return new ParceledListSlice<>(shortcutInfoList);
         }
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b55b6dd..7370d61 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -329,6 +329,8 @@
     static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
     static public final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
 
+    public static final String TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD = "waitForAllWindowsDrawn";
+
     private static final String TALKBACK_LABEL = "TalkBack";
 
     private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
@@ -566,6 +568,9 @@
     // What we do when the user double-taps on home
     private int mDoubleTapOnHomeBehavior;
 
+    // Whether to lock the device after the next app transition has finished.
+    private boolean mLockAfterAppTransitionFinished;
+
     // Allowed theater mode wake actions
     private boolean mAllowTheaterModeWakeFromKey;
     private boolean mAllowTheaterModeWakeFromPowerKey;
@@ -709,7 +714,7 @@
                     handleRingerChordGesture();
                     break;
                 case MSG_SCREENSHOT_CHORD:
-                    handleScreenShot(msg.arg1, msg.arg2);
+                    handleScreenShot(msg.arg1);
                     break;
             }
         }
@@ -972,12 +977,7 @@
             powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
         } else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
             Slog.d(TAG, "No behavior defined for power press count " + count);
-        } else if (count == 1 && interactive) {
-            if (beganFromNonInteractive) {
-                // The screen off case, where we might want to start dreaming on power button press.
-                attemptToDreamFromShortPowerButtonPress(false, () -> {});
-                return;
-            }
+        } else if (count == 1 && interactive && !beganFromNonInteractive) {
             if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
                 Slog.i(TAG, "Suppressing power key because the user is interacting with the "
                         + "fingerprint sensor");
@@ -1055,11 +1055,10 @@
             return;
         }
 
-        // Make sure the device locks. Unfortunately, this has the side-effect of briefly revealing
-        // the lock screen before the dream appears. Note that locking is a side-effect of the no
-        // dream action that is executed if we early return above.
-        // TODO(b/261662912): Find a better way to lock the device that doesn't result in jank.
-        lockNow(null);
+        synchronized (mLock) {
+            // Lock the device after the dream transition has finished.
+            mLockAfterAppTransitionFinished = true;
+        }
 
         dreamManagerInternal.requestDream();
     }
@@ -1500,9 +1499,9 @@
                 || mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
     }
 
-    private void interceptScreenshotChord(int type, int source, long pressDelay) {
+    private void interceptScreenshotChord(int source, long pressDelay) {
         mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, type, source),
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source),
                 pressDelay);
     }
 
@@ -1572,9 +1571,8 @@
         }
     };
 
-    private void handleScreenShot(@WindowManager.ScreenshotType int type,
-            @WindowManager.ScreenshotSource int source) {
-        mDefaultDisplayPolicy.takeScreenshot(type, source);
+    private void handleScreenShot(@WindowManager.ScreenshotSource int source) {
+        mDefaultDisplayPolicy.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, source);
     }
 
     @Override
@@ -2139,6 +2137,22 @@
                 handleStartTransitionForKeyguardLw(
                         keyguardGoingAway, false /* keyguardOccludingStarted */,
                         0 /* duration */);
+
+                synchronized (mLock) {
+                    mLockAfterAppTransitionFinished = false;
+                }
+            }
+
+            @Override
+            public void onAppTransitionFinishedLocked(IBinder token) {
+                synchronized (mLock) {
+                    if (!mLockAfterAppTransitionFinished) {
+                        return;
+                    }
+                    mLockAfterAppTransitionFinished = false;
+                }
+
+                lockNow(null);
             }
         });
 
@@ -2170,7 +2184,7 @@
                         @Override
                         void execute() {
                             mPowerKeyHandled = true;
-                            interceptScreenshotChord(TAKE_SCREENSHOT_FULLSCREEN,
+                            interceptScreenshotChord(
                                     SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
                         }
                         @Override
@@ -2864,8 +2878,7 @@
                 break;
             case KeyEvent.KEYCODE_S:
                 if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
-                    interceptScreenshotChord(
-                            TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+                    interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
                     return key_consumed;
                 }
                 break;
@@ -3239,8 +3252,7 @@
                 break;
             case KeyEvent.KEYCODE_SYSRQ:
                 if (down && repeatCount == 0) {
-                    interceptScreenshotChord(
-                            TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+                    interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
                 }
                 return true;
         }
@@ -4714,10 +4726,15 @@
 
         // ... eventually calls finishWindowsDrawn which will finalize our screen turn on
         // as well as enabling the orientation change logic/sensor.
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
+                TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
         mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
             if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for every display");
             mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
                     INVALID_DISPLAY, 0));
+
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
+                    TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
             }, WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY);
     }
 
@@ -4773,10 +4790,16 @@
             }
         } else {
             mScreenOnListeners.put(displayId, screenOnListener);
+
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
+                    TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
             mWindowManagerInternal.waitForAllWindowsDrawn(() -> {
                 if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId);
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE,
                         displayId, 0));
+
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER,
+                        TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0);
             }, WAITING_FOR_DRAWN_TIMEOUT, displayId);
         }
     }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index e7221c8..fde0b34 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -191,4 +191,12 @@
      * @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp
      */
     void enterStageSplitFromRunningApp(boolean leftOrTop);
+
+    /**
+     * Shows the media output switcher dialog.
+     *
+     * @param packageName of the session for which the output switcher is shown.
+     * @see com.android.internal.statusbar.IStatusBar#showMediaOutputSwitcher
+     */
+    void showMediaOutputSwitcher(String packageName);
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 45748e6..ab7292d 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -714,6 +714,16 @@
                 } catch (RemoteException ex) { }
             }
         }
+
+        @Override
+        public void showMediaOutputSwitcher(String packageName) {
+            if (mBar != null) {
+                try {
+                    mBar.showMediaOutputSwitcher(packageName);
+                } catch (RemoteException ex) {
+                }
+            }
+        }
     };
 
     private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7252545..7489f80 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -709,7 +709,8 @@
         synchronized (mGlobalLock) {
             final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
             return r != null
-                    ? r.getRequestedOrientation() : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+                    ? r.getOverrideOrientation()
+                    : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6b01a77..f7b9d8b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -439,6 +439,9 @@
     // finished destroying itself.
     private static final int DESTROY_TIMEOUT = 10 * 1000;
 
+    // Rounding tolerance to be used in aspect ratio computations
+    private static final float ASPECT_RATIO_ROUNDING_TOLERANCE = 0.005f;
+
     final ActivityTaskManagerService mAtmService;
     @NonNull
     final ActivityInfo info; // activity info provided by developer in AndroidManifest
@@ -1165,8 +1168,10 @@
             pw.println(prefix + "mVoiceInteraction=true");
         }
         pw.print(prefix); pw.print("mOccludesParent="); pw.println(mOccludesParent);
-        pw.print(prefix); pw.print("mOrientation=");
-        pw.println(ActivityInfo.screenOrientationToString(mOrientation));
+        pw.print(prefix); pw.print("overrideOrientation=");
+        pw.println(ActivityInfo.screenOrientationToString(getOverrideOrientation()));
+        pw.print(prefix); pw.print("requestedOrientation=");
+        pw.println(ActivityInfo.screenOrientationToString(super.getOverrideOrientation()));
         pw.println(prefix + "mVisibleRequested=" + mVisibleRequested
                 + " mVisible=" + mVisible + " mClientVisible=" + isClientVisible()
                 + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "")
@@ -1964,6 +1969,15 @@
                     new ComponentName(info.packageName, info.targetActivity);
         }
 
+        // Don't move below setActivityType since it triggers onConfigurationChange ->
+        // resolveOverrideConfiguration that requires having mLetterboxUiController initialised.
+        // Don't move below setOrientation(info.screenOrientation) since it triggers
+        // getOverrideOrientation that requires having mLetterboxUiController
+        // initialised.
+        mLetterboxUiController = new LetterboxUiController(mWmService, this);
+        mCameraCompatControlEnabled = mWmService.mContext.getResources()
+                .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
+
         mTargetSdk = info.applicationInfo.targetSdkVersion;
         mShowForAllUsers = (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0;
         setOrientation(info.screenOrientation);
@@ -2084,12 +2098,6 @@
 
         launchMode = aInfo.launchMode;
 
-        // Don't move below setActivityType since it triggers onConfigurationChange ->
-        // resolveOverrideConfiguration that requires having mLetterboxUiController initialised.
-        mLetterboxUiController = new LetterboxUiController(mWmService, this);
-        mCameraCompatControlEnabled = mWmService.mContext.getResources()
-                .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
-
         setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord);
 
         immersive = (aInfo.flags & FLAG_IMMERSIVE) != 0;
@@ -2486,7 +2494,8 @@
             if (topAttached != null) {
                 if (topAttached.isSnapshotCompatible(snapshot)
                         // This trampoline must be the same rotation.
-                        && mDisplayContent.getDisplayRotation().rotationForOrientation(mOrientation,
+                        && mDisplayContent.getDisplayRotation().rotationForOrientation(
+                                getOverrideOrientation(),
                                 mDisplayContent.getRotation()) == snapshot.getRotation()) {
                     return STARTING_WINDOW_TYPE_SNAPSHOT;
                 }
@@ -7704,13 +7713,13 @@
                 return mLetterboxUiController.getInheritedOrientation();
             }
         }
-        if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) {
+        if (task != null && getOverrideOrientation() == SCREEN_ORIENTATION_BEHIND) {
             // We use Task here because we want to be consistent with what happens in
             // multi-window mode where other tasks orientations are ignored.
             final ActivityRecord belowCandidate = task.getActivity(
-                    a -> a.mOrientation != SCREEN_ORIENTATION_UNSET && !a.finishing
-                            && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND, this,
-                    false /* includeBoundary */, true /* traverseTopToBottom */);
+                    a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
+                    this /* boundary */, false /* includeBoundary */,
+                    true /* traverseTopToBottom */);
             if (belowCandidate != null) {
                 return belowCandidate.getRequestedConfigurationOrientation(forDisplay);
             }
@@ -7718,6 +7727,19 @@
         return super.getRequestedConfigurationOrientation(forDisplay);
     }
 
+    /**
+     * Whether this activity can be used as an orientation source for activities above with
+     * {@link SCREEN_ORIENTATION_BEHIND}.
+     */
+    boolean canDefineOrientationForActivitiesAbove() {
+        if (finishing) {
+            return false;
+        }
+        final int overrideOrientation = getOverrideOrientation();
+        return overrideOrientation != SCREEN_ORIENTATION_UNSET
+                && overrideOrientation != SCREEN_ORIENTATION_BEHIND;
+    }
+
     @Override
     void onCancelFixedRotationTransform(int originalDisplayRotation) {
         if (this != mDisplayContent.getLastOrientationSource()) {
@@ -7744,7 +7766,7 @@
         }
     }
 
-    void setRequestedOrientation(int requestedOrientation) {
+    void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
         if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) {
             return;
         }
@@ -7789,7 +7811,7 @@
             // Allow app to specify orientation regardless of its visibility state if the current
             // candidate want us to use orientation behind. I.e. the visible app on-top of this one
             // wants us to use the orientation of the app behind it.
-            return mOrientation;
+            return getOverrideOrientation();
         }
 
         // The {@link ActivityRecord} should only specify an orientation when it is not closing.
@@ -7797,15 +7819,31 @@
         // task being started in the wrong orientation during the transition.
         if (!getDisplayContent().mClosingApps.contains(this)
                 && (isVisibleRequested() || getDisplayContent().mOpeningApps.contains(this))) {
-            return mOrientation;
+            return getOverrideOrientation();
         }
 
         return SCREEN_ORIENTATION_UNSET;
     }
 
-    /** Returns the app's preferred orientation regardless of its currently visibility state. */
+    /**
+     * Returns the app's preferred orientation regardless of its current visibility state taking
+     * into account orientation per-app overrides applied by the device manufacturers.
+     */
+    @Override
+    protected int getOverrideOrientation() {
+        return mLetterboxUiController.overrideOrientationIfNeeded(super.getOverrideOrientation());
+    }
+
+    /**
+     * Returns the app's preferred orientation regardless of its currently visibility state. This
+     * is used to return a requested value to an app if they call {@link
+     * android.app.Activity#getRequestedOrientation} since {@link #getOverrideOrientation} value
+     * with override can confuse an app if it's different from what they requested with {@link
+     * android.app.Activity#setRequestedOrientation}.
+     */
+    @ActivityInfo.ScreenOrientation
     int getRequestedOrientation() {
-        return mOrientation;
+        return super.getOverrideOrientation();
     }
 
     /**
@@ -8204,8 +8242,12 @@
             if (screenResolvedBounds.width() <= parentAppBounds.width()) {
                 float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
                         newParentConfiguration);
-                offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
-                        * positionMultiplier);
+                offsetX = Math.max(0, (int) Math.ceil((parentAppBounds.width()
+                        - screenResolvedBounds.width()) * positionMultiplier)
+                        // This is added to make sure that insets added inside
+                        // CompatDisplayInsets#getContainerBounds() do not break the alignment
+                        // provided by the positionMultiplier
+                        - screenResolvedBounds.left + parentAppBounds.left);
             }
         }
 
@@ -8215,8 +8257,12 @@
             if (screenResolvedBounds.height() <= parentAppBounds.height()) {
                 float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier(
                         newParentConfiguration);
-                offsetY = (int) Math.ceil((parentAppBounds.height() - screenResolvedBounds.height())
-                        * positionMultiplier);
+                offsetY = Math.max(0, (int) Math.ceil((parentAppBounds.height()
+                        - screenResolvedBounds.height()) * positionMultiplier)
+                        // This is added to make sure that insets added inside
+                        // CompatDisplayInsets#getContainerBounds() do not break the alignment
+                        // provided by the positionMultiplier
+                        - screenResolvedBounds.top + parentAppBounds.top);
             }
         }
 
@@ -8357,8 +8403,8 @@
         // If orientation is respected when insets are applied, then stableBounds will be empty.
         boolean orientationRespectedWithInsets =
                 orientationRespectedWithInsets(parentBounds, stableBounds);
-        if (orientationRespectedWithInsets
-                && handlesOrientationChangeFromDescendant(mOrientation)) {
+        if (orientationRespectedWithInsets && handlesOrientationChangeFromDescendant(
+                getOverrideOrientation())) {
             // No need to letterbox because of fixed orientation. Display will handle
             // fixed-orientation requests and a display rotation is enough to respect requested
             // orientation with insets applied.
@@ -8915,7 +8961,7 @@
         int activityWidth = containingAppWidth;
         int activityHeight = containingAppHeight;
 
-        if (containingRatio > desiredAspectRatio) {
+        if (containingRatio - desiredAspectRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
             if (containingAppWidth < containingAppHeight) {
                 // Width is the shorter side, so we use that to figure-out what the max. height
                 // should be given the aspect ratio.
@@ -8925,7 +8971,7 @@
                 // should be given the aspect ratio.
                 activityWidth = (int) ((activityHeight * desiredAspectRatio) + 0.5f);
             }
-        } else if (containingRatio < desiredAspectRatio) {
+        } else if (desiredAspectRatio - containingRatio > ASPECT_RATIO_ROUNDING_TOLERANCE) {
             boolean adjustWidth;
             switch (getRequestedConfigurationOrientation()) {
                 case ORIENTATION_LANDSCAPE:
@@ -9003,7 +9049,8 @@
         }
 
         if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
-                && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation())) {
+                && !ActivityInfo.isFixedOrientationPortrait(
+                        getOverrideOrientation())) {
             return info.getMinAspectRatio();
         }
 
@@ -9998,6 +10045,7 @@
                     isLandscape ? shortSide : longSide);
         }
 
+        // TODO(b/267151420): Explore removing getContainerBounds() from CompatDisplayInsets.
         /** Gets the horizontal centered container bounds for size compatibility mode. */
         void getContainerBounds(Rect outAppBounds, Rect outBounds, int rotation, int orientation,
                 boolean orientationRequested, boolean isFixedToUserRotation) {
@@ -10166,8 +10214,8 @@
     // TODO(b/263592337): Explore enabling compat fake focus for fullscreen, e.g. for when
     // covered with bubbles.
     boolean shouldSendCompatFakeFocus() {
-        return mWmService.mLetterboxConfiguration.isCompatFakeFocusEnabled(info)
-                && inMultiWindowMode() && !inPinnedWindowingMode() && !inFreeformWindowingMode();
+        return mLetterboxUiController.shouldSendFakeFocus() && inMultiWindowMode()
+                && !inPinnedWindowingMode() && !inFreeformWindowingMode();
     }
 
     static class Builder {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1ba7e68..8619615 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4715,6 +4715,7 @@
                 mTaskChangeNotificationController.notifyTaskFocusChanged(prevTask.mTaskId, false);
             }
             mTaskChangeNotificationController.notifyTaskFocusChanged(task.mTaskId, true);
+            mTaskSupervisor.mRecentTasks.add(task);
         }
 
         applyUpdateLockStateLocked(r);
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index a6f8557..7d9a4ec 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -16,80 +16,107 @@
 
 package com.android.server.wm;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 
+import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
- * Class that registers callbacks with the {@link DeviceStateManager} and
- * responds to fold state changes by forwarding such events to a delegate.
+ * Class that registers callbacks with the {@link DeviceStateManager} and responds to device
+ * changes.
  */
-final class DeviceStateController {
+final class DeviceStateController implements DeviceStateManager.DeviceStateCallback {
+
+    @NonNull
     private final DeviceStateManager mDeviceStateManager;
-    private final Context mContext;
+    @NonNull
+    private final int[] mOpenDeviceStates;
+    @NonNull
+    private final int[] mHalfFoldedDeviceStates;
+    @NonNull
+    private final int[] mFoldedDeviceStates;
+    @NonNull
+    private final int[] mRearDisplayDeviceStates;
+    @NonNull
+    private final int[] mReverseRotationAroundZAxisStates;
+    @NonNull
+    private final List<Consumer<DeviceState>> mDeviceStateCallbacks = new ArrayList<>();
 
-    private FoldStateListener mDeviceStateListener;
+    @Nullable
+    private DeviceState mLastDeviceState;
+    private int mCurrentState;
 
-    public enum FoldState {
-        UNKNOWN, OPEN, FOLDED, HALF_FOLDED
+    public enum DeviceState {
+        UNKNOWN, OPEN, FOLDED, HALF_FOLDED, REAR,
     }
 
-    DeviceStateController(Context context, Handler handler, Consumer<FoldState> delegate) {
-        mContext = context;
-        mDeviceStateManager = mContext.getSystemService(DeviceStateManager.class);
+    DeviceStateController(@NonNull Context context, @NonNull Handler handler) {
+        mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
+
+        mOpenDeviceStates = context.getResources()
+                .getIntArray(R.array.config_openDeviceStates);
+        mHalfFoldedDeviceStates = context.getResources()
+                .getIntArray(R.array.config_halfFoldedDeviceStates);
+        mFoldedDeviceStates = context.getResources()
+                .getIntArray(R.array.config_foldedDeviceStates);
+        mRearDisplayDeviceStates = context.getResources()
+                .getIntArray(R.array.config_rearDisplayDeviceStates);
+        mReverseRotationAroundZAxisStates = context.getResources()
+                .getIntArray(R.array.config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis);
+
         if (mDeviceStateManager != null) {
-            mDeviceStateListener = new FoldStateListener(mContext, delegate);
-            mDeviceStateManager
-                    .registerCallback(new HandlerExecutor(handler),
-                            mDeviceStateListener);
+            mDeviceStateManager.registerCallback(new HandlerExecutor(handler), this);
         }
     }
 
     void unregisterFromDeviceStateManager() {
-        if (mDeviceStateListener != null) {
-            mDeviceStateManager.unregisterCallback(mDeviceStateListener);
+        if (mDeviceStateManager != null) {
+            mDeviceStateManager.unregisterCallback(this);
         }
     }
 
+    void registerDeviceStateCallback(@NonNull Consumer<DeviceState> callback) {
+        mDeviceStateCallbacks.add(callback);
+    }
+
     /**
-     * A listener for half-fold device state events that dispatches state changes to a delegate.
+     * @return true if the rotation direction on the Z axis should be reversed.
      */
-    static final class FoldStateListener implements DeviceStateManager.DeviceStateCallback {
+    boolean shouldReverseRotationDirectionAroundZAxis() {
+        return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
+    }
 
-        private final int[] mHalfFoldedDeviceStates;
-        private final int[] mFoldedDeviceStates;
+    @Override
+    public void onStateChanged(int state) {
+        mCurrentState = state;
 
-        @Nullable
-        private FoldState mLastResult;
-        private final Consumer<FoldState> mDelegate;
-
-        FoldStateListener(Context context, Consumer<FoldState> delegate) {
-            mFoldedDeviceStates = context.getResources().getIntArray(
-                    com.android.internal.R.array.config_foldedDeviceStates);
-            mHalfFoldedDeviceStates = context.getResources().getIntArray(
-                    com.android.internal.R.array.config_halfFoldedDeviceStates);
-            mDelegate = delegate;
+        final DeviceState deviceState;
+        if (ArrayUtils.contains(mHalfFoldedDeviceStates, state)) {
+            deviceState = DeviceState.HALF_FOLDED;
+        } else if (ArrayUtils.contains(mFoldedDeviceStates, state)) {
+            deviceState = DeviceState.FOLDED;
+        } else if (ArrayUtils.contains(mRearDisplayDeviceStates, state)) {
+            deviceState = DeviceState.REAR;
+        } else if (ArrayUtils.contains(mOpenDeviceStates, state)) {
+            deviceState = DeviceState.OPEN;
+        } else {
+            deviceState = DeviceState.UNKNOWN;
         }
 
-        @Override
-        public void onStateChanged(int state) {
-            final boolean halfFolded = ArrayUtils.contains(mHalfFoldedDeviceStates, state);
-            FoldState result;
-            if (halfFolded) {
-                result = FoldState.HALF_FOLDED;
-            } else {
-                final boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
-                result = folded ? FoldState.FOLDED : FoldState.OPEN;
-            }
-            if (mLastResult == null || !mLastResult.equals(result)) {
-                mLastResult = result;
-                mDelegate.accept(result);
+        if (mLastDeviceState == null || !mLastDeviceState.equals(deviceState)) {
+            mLastDeviceState = deviceState;
+
+            for (Consumer<DeviceState> callback : mDeviceStateCallbacks) {
+                callback.accept(mLastDeviceState);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index af5bd14..b8870a1 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -93,7 +94,7 @@
     DisplayArea(WindowManagerService wms, Type type, String name, int featureId) {
         super(wms);
         // TODO(display-area): move this up to ConfigurationContainer
-        mOrientation = SCREEN_ORIENTATION_UNSET;
+        setOverrideOrientation(SCREEN_ORIENTATION_UNSET);
         mType = type;
         mName = name;
         mFeatureId = featureId;
@@ -165,7 +166,8 @@
         // If this is set to ignore the orientation request, we don't propagate descendant
         // orientation request.
         final int orientation = requestingContainer != null
-                ? requestingContainer.mOrientation : SCREEN_ORIENTATION_UNSET;
+                ? requestingContainer.getOverrideOrientation()
+                : SCREEN_ORIENTATION_UNSET;
         return !getIgnoreOrientationRequest(orientation)
                 && super.onDescendantOrientationChanged(requestingContainer);
     }
@@ -244,7 +246,22 @@
                 || orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
             return false;
         }
-        return getIgnoreOrientationRequest();
+        return getIgnoreOrientationRequest()
+                && !shouldRespectOrientationRequestDueToPerAppOverride();
+    }
+
+    private boolean shouldRespectOrientationRequestDueToPerAppOverride() {
+        if (mDisplayContent == null) {
+            return false;
+        }
+        ActivityRecord activity = mDisplayContent.topRunningActivity(
+                /* considerKeyguardState= */ true);
+        return activity != null && activity.getTaskFragment() != null
+                // Checking TaskFragment rather than ActivityRecord to ensure that transition
+                // between fullscreen and PiP would work well. Checking TaskFragment rather than
+                // Task to ensure that Activity Embedding is excluded.
+                && activity.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                && activity.mLetterboxUiController.isOverrideRespectRequestedOrientationEnabled();
     }
 
     boolean getIgnoreOrientationRequest() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 75d84ea..71a7e26 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -27,6 +27,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -1125,14 +1126,18 @@
                     mWmService.mAtmService.getRecentTasks().getInputListener());
         }
 
-        mDisplayPolicy = new DisplayPolicy(mWmService, this);
-        mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address);
+        mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH);
 
-        mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH,
-                newFoldState -> {
+        mDisplayPolicy = new DisplayPolicy(mWmService, this);
+        mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address,
+                mDeviceStateController);
+
+        final Consumer<DeviceStateController.DeviceState> deviceStateConsumer =
+                (@NonNull DeviceStateController.DeviceState newFoldState) -> {
                     mDisplaySwitchTransitionLauncher.foldStateChanged(newFoldState);
                     mDisplayRotation.foldStateChanged(newFoldState);
-                });
+                };
+        mDeviceStateController.registerDeviceStateCallback(deviceStateConsumer);
 
         mCloseToSquareMaxAspectRatio = mWmService.mContext.getResources().getFloat(
                 R.dimen.config_closeToSquareDisplayMaxAspectRatio);
@@ -1572,7 +1577,8 @@
         // If display rotation class tells us that it doesn't consider app requested orientation,
         // this display won't rotate just because of an app changes its requested orientation. Thus
         // it indicates that this display chooses not to handle this request.
-        final int orientation = requestingContainer != null ? requestingContainer.mOrientation
+        final int orientation = requestingContainer != null
+                ? requestingContainer.getOverrideOrientation()
                 : SCREEN_ORIENTATION_UNSET;
         final boolean handled = handlesOrientationChangeFromDescendant(orientation);
         if (config == null) {
@@ -1673,7 +1679,7 @@
             }
             // The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
             final ActivityRecord topCandidate = !r.isVisibleRequested() ? topRunningActivity() : r;
-            if (handleTopActivityLaunchingInDifferentOrientation(
+            if (topCandidate != null && handleTopActivityLaunchingInDifferentOrientation(
                     topCandidate, r, true /* checkOpening */)) {
                 // Display orientation should be deferred until the top fixed rotation is finished.
                 return false;
@@ -1698,15 +1704,15 @@
         if (mTransitionController.useShellTransitionsRotation()) {
             return ROTATION_UNDEFINED;
         }
+        final int activityOrientation = r.getOverrideOrientation();
         if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM
-                || getIgnoreOrientationRequest(r.mOrientation)) {
+                || getIgnoreOrientationRequest(activityOrientation)) {
             return ROTATION_UNDEFINED;
         }
-        if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+        if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
             final ActivityRecord nextCandidate = getActivity(
-                    a -> a.mOrientation != SCREEN_ORIENTATION_UNSET
-                            && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND,
-                    r, false /* includeBoundary */, true /* traverseTopToBottom */);
+                    a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
+                    r /* boundary */, false /* includeBoundary */, true /* traverseTopToBottom */);
             if (nextCandidate != null) {
                 r = nextCandidate;
             }
@@ -2110,6 +2116,10 @@
                 w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
             }, true /* traverseTopToBottom */);
             mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
+            if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
+                // Make sure DisplayRotation#isRotatingSeamlessly() will return false.
+                mDisplayRotation.cancelSeamlessRotation();
+            }
         }
 
         mWmService.mDisplayManagerInternal.performTraversal(transaction);
@@ -2723,6 +2733,15 @@
         final int orientation = super.getOrientation();
 
         if (!handlesOrientationChangeFromDescendant(orientation)) {
+            ActivityRecord topActivity = topRunningActivity(/* considerKeyguardState= */ true);
+            if (topActivity != null && topActivity.mLetterboxUiController
+                    .shouldUseDisplayLandscapeNaturalOrientation()) {
+                ProtoLog.v(WM_DEBUG_ORIENTATION,
+                        "Display id=%d is ignoring orientation request for %d, return %d"
+                        + " following a per-app override for %s",
+                        mDisplayId, orientation, SCREEN_ORIENTATION_LANDSCAPE, topActivity);
+                return SCREEN_ORIENTATION_LANDSCAPE;
+            }
             mLastOrientationSource = null;
             // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation
             ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -2974,7 +2993,7 @@
         if (density == mInitialDisplayDensity) {
             density = 0;
         }
-        mWmService.mDisplayWindowSettings.setForcedDensity(this, density, userId);
+        mWmService.mDisplayWindowSettings.setForcedDensity(getDisplayInfo(), density, userId);
     }
 
     /** @param mode {@link #FORCE_SCALING_MODE_AUTO} or {@link #FORCE_SCALING_MODE_DISABLED}. */
@@ -4809,7 +4828,7 @@
         mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
 
         mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
-        if (!mWmService.mDisplayFrozen) {
+        if (!mWmService.mDisplayFrozen && !mDisplayRotation.isRotatingSeamlessly()) {
             mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
                     mLastHasContent,
                     mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 0bb4022..cf7d5d9 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -142,6 +142,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.LetterboxDetails;
 import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
 import com.android.internal.util.function.TriConsumer;
 import com.android.internal.view.AppearanceRegion;
 import com.android.internal.widget.PointerLocationView;
@@ -2006,7 +2007,8 @@
                 dc.getDisplayPolicy().simulateLayoutDisplay(df);
                 final InsetsState insetsState = df.mInsetsState;
                 final Rect displayFrame = insetsState.getDisplayFrame();
-                final Insets decor = calculateDecorInsetsWithInternalTypes(insetsState);
+                final Insets decor = insetsState.calculateInsets(displayFrame, DECOR_TYPES,
+                        true /* ignoreVisibility */);
                 final Insets statusBar = insetsState.calculateInsets(displayFrame,
                         Type.statusBars(), true /* ignoreVisibility */);
                 mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
@@ -2038,17 +2040,8 @@
             }
         }
 
-        // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
-        static final int[] INTERNAL_DECOR_TYPES;
-        static {
-            final ArraySet<Integer> decorTypes = InsetsState.toInternalType(
-                    Type.displayCutout() | Type.navigationBars());
-            decorTypes.remove(ITYPE_EXTRA_NAVIGATION_BAR);
-            INTERNAL_DECOR_TYPES = new int[decorTypes.size()];
-            for (int i = 0; i < INTERNAL_DECOR_TYPES.length; i++) {
-                INTERNAL_DECOR_TYPES[i] = decorTypes.valueAt(i);
-            }
-        }
+
+        static final int DECOR_TYPES = Type.displayCutout() | Type.navigationBars();
 
         private final DisplayContent mDisplayContent;
         private final Info[] mInfoForRotation = new Info[4];
@@ -2075,20 +2068,6 @@
                 info.mNeedUpdate = true;
             }
         }
-
-        // TODO (b/235842600): Remove this method once we can treat task bar as navigation bar.
-        private static Insets calculateDecorInsetsWithInternalTypes(InsetsState state) {
-            final Rect frame = state.getDisplayFrame();
-            Insets insets = Insets.NONE;
-            for (int i = INTERNAL_DECOR_TYPES.length - 1; i >= 0; i--) {
-                final InsetsSource source = state.peekSource(INTERNAL_DECOR_TYPES[i]);
-                if (source != null) {
-                    insets = Insets.max(source.calculateInsets(frame, true /* ignoreVisibility */),
-                            insets);
-                }
-            }
-            return insets;
-        }
     }
 
     /**
@@ -2666,8 +2645,9 @@
      */
     public void takeScreenshot(int screenshotType, int source) {
         if (mScreenshotHelper != null) {
-            mScreenshotHelper.takeScreenshot(screenshotType,
-                    source, mHandler, null /* completionConsumer */);
+            ScreenshotRequest request =
+                    new ScreenshotRequest.Builder(screenshotType, source).build();
+            mScreenshotHelper.takeScreenshot(request, mHandler, null /* completionConsumer */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index e6d8b3d..3404279 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -41,6 +41,7 @@
 
 import android.annotation.AnimRes;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.ContentResolver;
@@ -117,6 +118,8 @@
     private SettingsObserver mSettingsObserver;
     @Nullable
     private FoldController mFoldController;
+    @NonNull
+    private final DeviceStateController mDeviceStateController;
 
     @ScreenOrientation
     private int mCurrentAppOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -218,21 +221,24 @@
     private boolean mDemoRotationLock;
 
     DisplayRotation(WindowManagerService service, DisplayContent displayContent,
-            DisplayAddress displayAddress) {
+            DisplayAddress displayAddress, @NonNull DeviceStateController deviceStateController) {
         this(service, displayContent, displayAddress, displayContent.getDisplayPolicy(),
-                service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock());
+                service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock(),
+                deviceStateController);
     }
 
     @VisibleForTesting
     DisplayRotation(WindowManagerService service, DisplayContent displayContent,
             DisplayAddress displayAddress, DisplayPolicy displayPolicy,
-            DisplayWindowSettings displayWindowSettings, Context context, Object lock) {
+            DisplayWindowSettings displayWindowSettings, Context context, Object lock,
+            @NonNull DeviceStateController deviceStateController) {
         mService = service;
         mDisplayContent = displayContent;
         mDisplayPolicy = displayPolicy;
         mDisplayWindowSettings = displayWindowSettings;
         mContext = context;
         mLock = lock;
+        mDeviceStateController = deviceStateController;
         isDefaultDisplay = displayContent.isDefaultDisplay;
         mCompatPolicyForImmersiveApps = initImmersiveAppCompatPolicy(service, displayContent);
 
@@ -243,11 +249,13 @@
         mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
         mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
 
-        mRotation = readDefaultDisplayRotation(displayAddress);
+        int defaultRotation = readDefaultDisplayRotation(displayAddress);
+        mRotation = defaultRotation;
 
         if (isDefaultDisplay) {
             final Handler uiHandler = UiThread.getHandler();
-            mOrientationListener = new OrientationListener(mContext, uiHandler);
+            mOrientationListener =
+                    new OrientationListener(mContext, uiHandler, defaultRotation);
             mOrientationListener.setCurrentRotation(mRotation);
             mSettingsObserver = new SettingsObserver(uiHandler);
             mSettingsObserver.observe();
@@ -1137,6 +1145,15 @@
         int sensorRotation = mOrientationListener != null
                 ? mOrientationListener.getProposedRotation() // may be -1
                 : -1;
+        if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+            // Flipping 270 and 90 has the same effect as changing the direction which rotation is
+            // applied.
+            if (sensorRotation == Surface.ROTATION_90) {
+                sensorRotation = Surface.ROTATION_270;
+            } else if (sensorRotation == Surface.ROTATION_270) {
+                sensorRotation = Surface.ROTATION_90;
+            }
+        }
         mLastSensorRotation = sensorRotation;
         if (sensorRotation < 0) {
             sensorRotation = lastRotation;
@@ -1573,7 +1590,7 @@
         proto.end(token);
     }
 
-    boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) {
+    boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
         if (mFoldController == null) return false;
         return mFoldController.isDeviceInPosture(state, isTabletop);
     }
@@ -1585,10 +1602,10 @@
     /**
      * Called by the DeviceStateManager callback when the device state changes.
      */
-    void foldStateChanged(DeviceStateController.FoldState foldState) {
+    void foldStateChanged(DeviceStateController.DeviceState deviceState) {
         if (mFoldController != null) {
             synchronized (mLock) {
-                mFoldController.foldStateChanged(foldState);
+                mFoldController.foldStateChanged(deviceState);
             }
         }
     }
@@ -1596,8 +1613,8 @@
     private class FoldController {
         @Surface.Rotation
         private int mHalfFoldSavedRotation = -1; // No saved rotation
-        private DeviceStateController.FoldState mFoldState =
-                DeviceStateController.FoldState.UNKNOWN;
+        private DeviceStateController.DeviceState mDeviceState =
+                DeviceStateController.DeviceState.UNKNOWN;
         private boolean mInHalfFoldTransition = false;
         private final boolean mIsDisplayAlwaysSeparatingHinge;
         private final Set<Integer> mTabletopRotations;
@@ -1637,32 +1654,33 @@
                     R.bool.config_isDisplayHingeAlwaysSeparating);
         }
 
-        boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) {
-            if (state != mFoldState) {
+        boolean isDeviceInPosture(DeviceStateController.DeviceState state, boolean isTabletop) {
+            if (state != mDeviceState) {
                 return false;
             }
-            if (mFoldState == DeviceStateController.FoldState.HALF_FOLDED) {
+            if (mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) {
                 return !(isTabletop ^ mTabletopRotations.contains(mRotation));
             }
             return true;
         }
 
-        DeviceStateController.FoldState getFoldState() {
-            return mFoldState;
+        DeviceStateController.DeviceState getFoldState() {
+            return mDeviceState;
         }
 
         boolean isSeparatingHinge() {
-            return mFoldState == DeviceStateController.FoldState.HALF_FOLDED
-                    || (mFoldState == DeviceStateController.FoldState.OPEN
+            return mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED
+                    || (mDeviceState == DeviceStateController.DeviceState.OPEN
                         && mIsDisplayAlwaysSeparatingHinge);
         }
 
         boolean overrideFrozenRotation() {
-            return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
+            return mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED;
         }
 
         boolean shouldRevertOverriddenRotation() {
-            return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open.
+            // When transitioning to open.
+            return mDeviceState == DeviceStateController.DeviceState.OPEN
                     && mInHalfFoldTransition
                     && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
                     && mUserRotationMode
@@ -1676,30 +1694,30 @@
             return savedRotation;
         }
 
-        void foldStateChanged(DeviceStateController.FoldState newState) {
+        void foldStateChanged(DeviceStateController.DeviceState newState) {
             ProtoLog.v(WM_DEBUG_ORIENTATION,
                     "foldStateChanged: displayId %d, halfFoldStateChanged %s, "
                     + "saved rotation: %d, mUserRotation: %d, mLastSensorRotation: %d, "
                     + "mLastOrientation: %d, mRotation: %d",
                     mDisplayContent.getDisplayId(), newState.name(), mHalfFoldSavedRotation,
                     mUserRotation, mLastSensorRotation, mLastOrientation, mRotation);
-            if (mFoldState == DeviceStateController.FoldState.UNKNOWN) {
-                mFoldState = newState;
+            if (mDeviceState == DeviceStateController.DeviceState.UNKNOWN) {
+                mDeviceState = newState;
                 return;
             }
-            if (newState == DeviceStateController.FoldState.HALF_FOLDED
-                    && mFoldState != DeviceStateController.FoldState.HALF_FOLDED) {
+            if (newState == DeviceStateController.DeviceState.HALF_FOLDED
+                    && mDeviceState != DeviceStateController.DeviceState.HALF_FOLDED) {
                 // The device has transitioned to HALF_FOLDED state: save the current rotation and
                 // update the device rotation.
                 mHalfFoldSavedRotation = mRotation;
-                mFoldState = newState;
+                mDeviceState = newState;
                 // Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will
                 // return true, so rotation is unlocked.
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
             } else {
                 mInHalfFoldTransition = true;
-                mFoldState = newState;
+                mDeviceState = newState;
                 // Tell the device to update its orientation.
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
@@ -1719,8 +1737,9 @@
     private class OrientationListener extends WindowOrientationListener implements Runnable {
         transient boolean mEnabled;
 
-        OrientationListener(Context context, Handler handler) {
-            super(context, handler);
+        OrientationListener(Context context, Handler handler,
+                @Surface.Rotation int defaultRotation) {
+            super(context, handler, defaultRotation);
         }
 
         @Override
@@ -1822,7 +1841,7 @@
             final long mTimestamp = System.currentTimeMillis();
             final int mHalfFoldSavedRotation;
             final boolean mInHalfFoldTransition;
-            final DeviceStateController.FoldState mFoldState;
+            final DeviceStateController.DeviceState mDeviceState;
             @Nullable final String mDisplayRotationCompatPolicySummary;
 
             Record(DisplayRotation dr, int fromRotation, int toRotation) {
@@ -1843,8 +1862,9 @@
                 if (source != null) {
                     mLastOrientationSource = source.toString();
                     final WindowState w = source.asWindowState();
-                    mSourceOrientation =
-                            w != null ? w.mAttrs.screenOrientation : source.mOrientation;
+                    mSourceOrientation = w != null
+                            ? w.mAttrs.screenOrientation
+                            : source.getOverrideOrientation();
                 } else {
                     mLastOrientationSource = null;
                     mSourceOrientation = SCREEN_ORIENTATION_UNSET;
@@ -1852,11 +1872,11 @@
                 if (dr.mFoldController != null) {
                     mHalfFoldSavedRotation = dr.mFoldController.mHalfFoldSavedRotation;
                     mInHalfFoldTransition = dr.mFoldController.mInHalfFoldTransition;
-                    mFoldState = dr.mFoldController.mFoldState;
+                    mDeviceState = dr.mFoldController.mDeviceState;
                 } else {
                     mHalfFoldSavedRotation = NO_FOLD_CONTROLLER;
                     mInHalfFoldTransition = false;
-                    mFoldState = DeviceStateController.FoldState.UNKNOWN;
+                    mDeviceState = DeviceStateController.DeviceState.UNKNOWN;
                 }
                 mDisplayRotationCompatPolicySummary = dc.mDisplayRotationCompatPolicy == null
                         ? null
@@ -1882,7 +1902,7 @@
                     pw.println(prefix + " halfFoldSavedRotation="
                             + mHalfFoldSavedRotation
                             + " mInHalfFoldTransition=" + mInHalfFoldTransition
-                            + " mFoldState=" + mFoldState);
+                            + " mFoldState=" + mDeviceState);
                 }
                 if (mDisplayRotationCompatPolicySummary != null) {
                     pw.println(prefix + mDisplayRotationCompatPolicySummary);
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index c6037da..e04900c 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
@@ -34,6 +36,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.RefreshCallbackItem;
 import android.app.servertransaction.ResumeActivityItem;
@@ -44,10 +47,13 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.widget.Toast;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.UiThread;
 
 import java.util.Map;
 import java.util.Set;
@@ -232,6 +238,27 @@
         activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
     }
 
+    /**
+     * Notifies that animation in {@link ScreenAnimationRotation} has finished.
+     *
+     * <p>This class uses this signal as a trigger for notifying the user about forced rotation
+     * reason with the {@link Toast}.
+     */
+    void onScreenRotationAnimationFinished() {
+        if (!isTreatmentEnabledForDisplay() || mCameraIdPackageBiMap.isEmpty()) {
+            return;
+        }
+        ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+                    /* considerKeyguardState= */ true);
+        if (topActivity == null
+                // Checking windowing mode on activity level because we don't want to
+                // show toast in case of activity embedding.
+                || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+            return;
+        }
+        showToast(R.string.display_rotation_camera_compat_toast_after_rotation);
+    }
+
     String getSummaryForDisplayRotationHistoryRecord() {
         String summaryIfEnabled = "";
         if (isTreatmentEnabledForDisplay()) {
@@ -281,6 +308,10 @@
                 && mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
     }
 
+    boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
+        return isTreatmentEnabledForDisplay() && isCameraActiveInFullscreen(activity);
+    }
+
     /**
      * Whether camera compat treatment is applicable for the given activity.
      *
@@ -292,12 +323,16 @@
      * </ul>
      */
     boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
-        return activity != null && !activity.inMultiWindowMode()
+        return activity != null && isCameraActiveInFullscreen(activity)
                 && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
                 // "locked" and "nosensor" values are often used by camera apps that can't
                 // handle dynamic changes so we shouldn't force rotate them.
-                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
-                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
+                && activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
+                && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
+    }
+
+    private boolean isCameraActiveInFullscreen(@NonNull ActivityRecord activity) {
+        return !activity.inMultiWindowMode()
                 && mCameraIdPackageBiMap.containsPackageName(activity.packageName)
                 && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
     }
@@ -334,7 +369,31 @@
             }
             mCameraIdPackageBiMap.put(packageName, cameraId);
         }
-        updateOrientationWithWmLock();
+        ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+                    /* considerKeyguardState= */ true);
+        if (topActivity == null || topActivity.getTask() == null) {
+            return;
+        }
+        // Checking whether an activity in fullscreen rather than the task as this camera compat
+        // treatment doesn't cover activity embedding.
+        if (topActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+            if (topActivity.mLetterboxUiController.isOverrideOrientationOnlyForCameraEnabled()) {
+                topActivity.recomputeConfiguration();
+            }
+            updateOrientationWithWmLock();
+            return;
+        }
+        // Checking that the whole app is in multi-window mode as we shouldn't show toast
+        // for the activity embedding case.
+        if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+            showToast(R.string.display_rotation_camera_compat_toast_in_split_screen);
+        }
+    }
+
+    @VisibleForTesting
+    void showToast(@StringRes int stringRes) {
+        UiThread.getHandler().post(
+                () -> Toast.makeText(mWmService.mContext, stringRes, Toast.LENGTH_LONG).show());
     }
 
     private synchronized void notifyCameraClosed(@NonNull String cameraId) {
@@ -375,6 +434,17 @@
         ProtoLog.v(WM_DEBUG_ORIENTATION,
                 "Display id=%d is notified that Camera %s is closed, updating rotation.",
                 mDisplayContent.mDisplayId, cameraId);
+        ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+                /* considerKeyguardState= */ true);
+        if (topActivity == null
+                // Checking whether an activity in fullscreen rather than the task as this camera
+                // compat treatment doesn't cover activity embedding.
+                || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+            return;
+        }
+        if (topActivity.mLetterboxUiController.isOverrideOrientationOnlyForCameraEnabled()) {
+            topActivity.recomputeConfiguration();
+        }
         updateOrientationWithWmLock();
     }
 
@@ -396,6 +466,10 @@
         private final Map<String, String> mPackageToCameraIdMap = new ArrayMap<>();
         private final Map<String, String> mCameraIdToPackageMap = new ArrayMap<>();
 
+        boolean isEmpty() {
+            return mCameraIdToPackageMap.isEmpty();
+        }
+
         void put(String packageName, String cameraId) {
             // Always using the last connected camera ID for the package even for the concurrent
             // camera use case since we can't guess which camera is more important anyway.
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index c1b6496..d87532a 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -77,14 +77,14 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
-    void setForcedDensity(DisplayContent displayContent, int density, int userId) {
-        if (displayContent.isDefaultDisplay) {
+    void setForcedDensity(DisplayInfo info, int density, int userId) {
+        if (info.displayId == Display.DEFAULT_DISPLAY) {
             final String densityString = density == 0 ? "" : Integer.toString(density);
             Settings.Secure.putStringForUser(mService.mContext.getContentResolver(),
                     Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId);
         }
 
-        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final DisplayInfo displayInfo = info;
         final SettingsProvider.SettingsEntry overrideSettings =
                 mSettingsProvider.getOverrideSettings(displayInfo);
         overrideSettings.mForcedDensity = density;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index f916ee4..7066a33 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -18,14 +18,14 @@
 
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT;
 import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
 import android.graphics.Color;
 import android.provider.DeviceConfig;
 import android.util.Slog;
@@ -197,6 +197,12 @@
     // Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
     private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
 
+    // Whether using display aspect ratio as a default aspect ratio for all letterboxed apps.
+    // mIsSplitScreenAspectRatioForUnresizableAppsEnabled and
+    // config_letterboxDefaultMinAspectRatioForUnresizableApps take priority over this for
+    // unresizable apps
+    private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox;
+
     // Whether letterboxing strategy is enabled for translucent activities. If {@value false}
     // all the feature is disabled
     private boolean mTranslucentLetterboxingEnabled;
@@ -288,6 +294,9 @@
                 R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
         mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+        mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox = mContext.getResources()
+                .getBoolean(R.bool
+                        .config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled);
         mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsEnabledForTranslucentActivities);
         mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
@@ -300,14 +309,28 @@
         mIsDisplayRotationImmersiveAppCompatPolicyEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled);
         mDeviceConfig.updateFlagActiveStatus(
+                /* isActive */ mIsCameraCompatTreatmentEnabled,
+                /* key */ KEY_ENABLE_CAMERA_COMPAT_TREATMENT);
+        mDeviceConfig.updateFlagActiveStatus(
                 /* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled,
                 /* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
+        mDeviceConfig.updateFlagActiveStatus(
+                /* isActive */ true,
+                /* key */ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
 
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
     }
 
     /**
+     * Whether enabling ignoreOrientationRequest is allowed on the device. This value is controlled
+     * via {@link android.provider.DeviceConfig}.
+     */
+    boolean isIgnoreOrientationRequestAllowed() {
+        return mDeviceConfig.getFlag(KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
+    }
+
+    /**
      * Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link
      * #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link
      * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
@@ -943,6 +966,13 @@
     }
 
     /**
+     * Whether using display aspect ratio as a default aspect ratio for all letterboxed apps.
+     */
+    boolean getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox() {
+        return mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox;
+    }
+
+    /**
      * Overrides whether using split screen aspect ratio as a default aspect ratio for unresizable
      * apps.
      */
@@ -951,6 +981,14 @@
     }
 
     /**
+     * Overrides whether using display aspect ratio as a default aspect ratio for all letterboxed
+     * apps.
+     */
+    void setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(boolean enabled) {
+        mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox = enabled;
+    }
+
+    /**
      * Resets whether using split screen aspect ratio as a default aspect ratio for unresizable
      * apps {@link R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled}.
      */
@@ -959,6 +997,16 @@
                 R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
     }
 
+    /**
+     * Resets whether using display aspect ratio as a default aspect ratio for all letterboxed
+     * apps {@link R.bool.config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled}.
+     */
+    void resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox() {
+        mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox = mContext.getResources()
+                .getBoolean(R.bool
+                        .config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled);
+    }
+
     boolean isTranslucentLetterboxingEnabled() {
         return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled
                 && isTranslucentLetterboxingAllowed());
@@ -1016,38 +1064,8 @@
                 "enable_translucent_activity_letterbox", false);
     }
 
-    @VisibleForTesting
-    boolean getPackageManagerProperty(PackageManager pm, String property) {
-        boolean enabled = false;
-        try {
-            final PackageManager.Property p = pm.getProperty(property, mContext.getPackageName());
-            enabled = p.getBoolean();
-        } catch (PackageManager.NameNotFoundException e) {
-            // Property not found
-        }
-        return enabled;
-    }
-
-    @VisibleForTesting
-    boolean isCompatFakeFocusEnabled(ActivityInfo info) {
-        if (!isCompatFakeFocusEnabledOnDevice()) {
-            return false;
-        }
-        // See if the developer has chosen to opt in / out of treatment
-        PackageManager pm = mContext.getPackageManager();
-        if (getPackageManagerProperty(pm, PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT)) {
-            return false;
-        } else if (getPackageManagerProperty(pm, PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN)) {
-            return true;
-        }
-        if (info.isChangeEnabled(ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS)) {
-            return true;
-        }
-        return false;
-    }
-
     /** Whether fake sending focus is enabled for unfocused apps in splitscreen */
-    boolean isCompatFakeFocusEnabledOnDevice() {
+    boolean isCompatFakeFocusEnabled() {
         return mIsCompatFakeFocusEnabled
                 && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                         DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, true);
@@ -1072,15 +1090,8 @@
 
     /** Whether camera compatibility treatment is enabled. */
     boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
-        return mIsCameraCompatTreatmentEnabled
-                && (!checkDeviceConfig || isCameraCompatTreatmentAllowed());
-    }
-
-    // TODO(b/262977416): Cache a runtime flag and implement
-    // DeviceConfig.OnPropertiesChangedListener
-    private static boolean isCameraCompatTreatmentAllowed() {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                "enable_compat_camera_treatment", true);
+        return mIsCameraCompatTreatmentEnabled && (!checkDeviceConfig
+                || mDeviceConfig.getFlag(KEY_ENABLE_CAMERA_COMPAT_TREATMENT));
     }
 
     /** Whether camera compatibility refresh is enabled. */
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
index cf123a1..d004fa6 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -33,17 +33,33 @@
 final class LetterboxConfigurationDeviceConfig
         implements DeviceConfig.OnPropertiesChangedListener {
 
+    static final String KEY_ENABLE_CAMERA_COMPAT_TREATMENT = "enable_compat_camera_treatment";
+    private static final boolean DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT = true;
+
     static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
             "enable_display_rotation_immersive_app_compat_policy";
     private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
             true;
 
+    static final String KEY_ALLOW_IGNORE_ORIENTATION_REQUEST =
+            "allow_ignore_orientation_request";
+    private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
+
     @VisibleForTesting
     static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
+            KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
+            DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT,
             KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
-            DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY
+            DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
+            KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
+            DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST
     );
 
+    // Whether camera compatibility treatment is enabled.
+    // See DisplayRotationCompatPolicy for context.
+    private boolean mIsCameraCompatTreatmentEnabled =
+            DEFAULT_VALUE_ENABLE_CAMERA_COMPAT_TREATMENT;
+
     // Whether enabling rotation compat policy for immersive apps that prevents auto rotation
     // into non-optimal screen orientation while in fullscreen. This is needed because immersive
     // apps, such as games, are often not optimized for all orientations and can have a poor UX
@@ -52,6 +68,10 @@
     private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
             DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
 
+    // Whether enabling ignoreOrientationRequest is allowed on the device.
+    private boolean mIsAllowIgnoreOrientationRequest =
+            DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST;
+
     // Set of active device configs that need to be updated in
     // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
     private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
@@ -91,8 +111,12 @@
      */
     boolean getFlag(String key) {
         switch (key) {
+            case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
+                return mIsCameraCompatTreatmentEnabled;
             case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
                 return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
+            case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
+                return mIsAllowIgnoreOrientationRequest;
             default:
                 throw new AssertionError("Unexpected flag name: " + key);
         }
@@ -104,10 +128,18 @@
             throw new AssertionError("Haven't found default value for flag: " + key);
         }
         switch (key) {
+            case KEY_ENABLE_CAMERA_COMPAT_TREATMENT:
+                mIsCameraCompatTreatmentEnabled =
+                        getDeviceConfig(key, defaultValue);
+                break;
             case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
                 mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
                         getDeviceConfig(key, defaultValue);
                 break;
+            case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
+                mIsAllowIgnoreOrientationRequest =
+                        getDeviceConfig(key, defaultValue);
+                break;
             default:
                 throw new AssertionError("Unexpected flag name: " + key);
         }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9c43c1d..9681789 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,11 +17,24 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
+import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
+import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.isFixedOrientation;
+import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
 import static android.content.pm.ActivityInfo.screenOrientationToString;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -29,6 +42,9 @@
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
@@ -62,6 +78,9 @@
 import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
 import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
 
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager.TaskDescription;
 import android.content.pm.ActivityInfo.ScreenOrientation;
@@ -105,6 +124,47 @@
 
     private final ActivityRecord mActivityRecord;
 
+    /**
+     * Taskbar expanded height. Used to determine when to crop an app window to display the
+     * rounded corners above the expanded taskbar.
+     */
+    private final float mExpandedTaskBarHeight;
+
+    // TODO(b/265576778): Cache other overrides as well.
+
+    // Corresponds to OVERRIDE_ANY_ORIENTATION
+    private final boolean mIsOverrideAnyOrientationEnabled;
+    // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
+    private final boolean mIsOverrideToPortraitOrientationEnabled;
+    // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
+    private final boolean mIsOverrideToNosensorOrientationEnabled;
+    // Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE
+    private final boolean mIsOverrideToReverseLandscapeOrientationEnabled;
+    // Corresponds to OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA
+    private final boolean mIsOverrideOrientationOnlyForCameraEnabled;
+    // Corresponds to OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION
+    private final boolean mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled;
+    // Corresponds to OVERRIDE_RESPECT_REQUESTED_ORIENTATION
+    private final boolean mIsOverrideRespectRequestedOrientationEnabled;
+
+    // Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION
+    private final boolean mIsOverrideCameraCompatDisableForceRotationEnabled;
+    // Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH
+    private final boolean mIsOverrideCameraCompatDisableRefreshEnabled;
+    // Corresponds to OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE
+    private final boolean mIsOverrideCameraCompatEnableRefreshViaPauseEnabled;
+
+    // Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION
+    private final boolean mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled;
+
+    // Corresponds to OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS
+    private final boolean mIsOverrideEnableCompatFakeFocusEnabled;
+
+    @Nullable
+    private final Boolean mBooleanPropertyAllowOrientationOverride;
+    @Nullable
+    private final Boolean mBooleanPropertyAllowDisplayOrientationOverride;
+
     /*
      * WindowContainerListener responsible to make translucent activities inherit
      * constraints from the first opaque activity beneath them. It's null for not
@@ -155,6 +215,9 @@
     @Nullable
     private final Boolean mBooleanPropertyIgnoreRequestedOrientation;
 
+    @Nullable
+    private final Boolean mBooleanPropertyFakeFocus;
+
     private boolean mIsRelauchingAfterRequestedOrientationChanged;
 
     LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
@@ -169,6 +232,10 @@
                 readComponentProperty(packageManager, mActivityRecord.packageName,
                         mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
                         PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+        mBooleanPropertyFakeFocus =
+                readComponentProperty(packageManager, mActivityRecord.packageName,
+                        mLetterboxConfiguration::isCompatFakeFocusEnabled,
+                        PROPERTY_COMPAT_ENABLE_FAKE_FOCUS);
         mBooleanPropertyCameraCompatAllowForceRotation =
                 readComponentProperty(packageManager, mActivityRecord.packageName,
                         () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
@@ -184,6 +251,45 @@
                         () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
                                 /* checkDeviceConfig */ true),
                         PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
+
+        mExpandedTaskBarHeight =
+                getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
+
+        mBooleanPropertyAllowOrientationOverride =
+                readComponentProperty(packageManager, mActivityRecord.packageName,
+                        /* gatingCondition */ null,
+                        PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
+        mBooleanPropertyAllowDisplayOrientationOverride =
+                readComponentProperty(packageManager, mActivityRecord.packageName,
+                        /* gatingCondition */ null,
+                        PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE);
+
+        mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
+        mIsOverrideToPortraitOrientationEnabled =
+                isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
+        mIsOverrideToReverseLandscapeOrientationEnabled =
+                isCompatChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE);
+        mIsOverrideToNosensorOrientationEnabled =
+                isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR);
+        mIsOverrideOrientationOnlyForCameraEnabled =
+                isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
+        mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled =
+                isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION);
+        mIsOverrideRespectRequestedOrientationEnabled =
+                isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
+
+        mIsOverrideCameraCompatDisableForceRotationEnabled =
+                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION);
+        mIsOverrideCameraCompatDisableRefreshEnabled =
+                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH);
+        mIsOverrideCameraCompatEnableRefreshViaPauseEnabled =
+                isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
+
+        mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled =
+                isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+
+        mIsOverrideEnableCompatFakeFocusEnabled =
+                isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS);
     }
 
     /**
@@ -198,8 +304,8 @@
      */
     @Nullable
     private static Boolean readComponentProperty(PackageManager packageManager, String packageName,
-            BooleanSupplier gatingCondition, String propertyName) {
-        if (!gatingCondition.getAsBoolean()) {
+            @Nullable BooleanSupplier gatingCondition, String propertyName) {
+        if (gatingCondition != null && !gatingCondition.getAsBoolean()) {
             return null;
         }
         try {
@@ -253,7 +359,7 @@
         if (!shouldEnableWithOverrideAndProperty(
                 /* gatingCondition */ mLetterboxConfiguration
                         ::isPolicyForIgnoringRequestedOrientationEnabled,
-                OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION,
+                mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled,
                 mBooleanPropertyIgnoreRequestedOrientation)) {
             return false;
         }
@@ -279,6 +385,25 @@
     }
 
     /**
+     * Whether sending compat fake focus for split screen resumed activities is enabled. Needed
+     * because some game engines wait to get focus before drawing the content of the app which isn't
+     * guaranteed by default in multi-window modes.
+     *
+     * <p>This treatment is enabled when the following conditions are met:
+     * <ul>
+     *     <li>Flag gating the treatment is enabled
+     *     <li>Component property is NOT set to false
+     *     <li>Component property is set to true or per-app override is enabled
+     * </ul>
+     */
+    boolean shouldSendFakeFocus() {
+        return shouldEnableWithOverrideAndProperty(
+                /* gatingCondition */ mLetterboxConfiguration::isCompatFakeFocusEnabled,
+                mIsOverrideEnableCompatFakeFocusEnabled,
+                mBooleanPropertyFakeFocus);
+    }
+
+    /**
      * Sets whether an activity is relaunching after the app has called {@link
      * android.app.Activity#setRequestedOrientation}.
      */
@@ -298,6 +423,82 @@
         mIsRefreshAfterRotationRequested = isRequested;
     }
 
+    boolean isOverrideRespectRequestedOrientationEnabled() {
+        return mIsOverrideRespectRequestedOrientationEnabled;
+    }
+
+    /**
+     * Whether should fix display orientation to landscape natural orientation when a task is
+     * fullscreen and the display is ignoring orientation requests.
+     *
+     * <p>This treatment is enabled when the following conditions are met:
+     * <ul>
+     *     <li>Opt-out component property isn't enabled
+     *     <li>Opt-in per-app override is enabled
+     *     <li>Task is in fullscreen.
+     *     <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled
+     *     <li>Natural orientation of the display is landscape.
+     * </ul>
+     */
+    boolean shouldUseDisplayLandscapeNaturalOrientation() {
+        return shouldEnableWithOptInOverrideAndOptOutProperty(
+                /* gatingCondition */ () -> mActivityRecord.mDisplayContent != null
+                        && mActivityRecord.getTask() != null
+                        && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()
+                        && !mActivityRecord.getTask().inMultiWindowMode()
+                        && mActivityRecord.mDisplayContent.getNaturalOrientation()
+                                == ORIENTATION_LANDSCAPE,
+                mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled,
+                mBooleanPropertyAllowDisplayOrientationOverride);
+    }
+
+    @ScreenOrientation
+    int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
+        if (FALSE.equals(mBooleanPropertyAllowOrientationOverride)) {
+            return candidate;
+        }
+
+        DisplayContent displayContent = mActivityRecord.mDisplayContent;
+        if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null
+                && (displayContent.mDisplayRotationCompatPolicy == null
+                        || !displayContent.mDisplayRotationCompatPolicy
+                                .isActivityEligibleForOrientationOverride(mActivityRecord))) {
+            return candidate;
+        }
+
+        if (mIsOverrideToReverseLandscapeOrientationEnabled
+                && (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) {
+            Slog.w(TAG, "Requested orientation  " + screenOrientationToString(candidate) + " for "
+                    + mActivityRecord + " is overridden to "
+                    + screenOrientationToString(SCREEN_ORIENTATION_REVERSE_LANDSCAPE));
+            return SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+        }
+
+        if (!mIsOverrideAnyOrientationEnabled && isFixedOrientation(candidate)) {
+            return candidate;
+        }
+
+        if (mIsOverrideToPortraitOrientationEnabled) {
+            Slog.w(TAG, "Requested orientation  " + screenOrientationToString(candidate) + " for "
+                    + mActivityRecord + " is overridden to "
+                    + screenOrientationToString(SCREEN_ORIENTATION_PORTRAIT));
+            return SCREEN_ORIENTATION_PORTRAIT;
+        }
+
+        if (mIsOverrideToNosensorOrientationEnabled) {
+            Slog.w(TAG, "Requested orientation  " + screenOrientationToString(candidate) + " for "
+                    + mActivityRecord + " is overridden to "
+                    + screenOrientationToString(SCREEN_ORIENTATION_NOSENSOR));
+            return SCREEN_ORIENTATION_NOSENSOR;
+        }
+
+        return candidate;
+    }
+
+    boolean isOverrideOrientationOnlyForCameraEnabled() {
+        return mIsOverrideOrientationOnlyForCameraEnabled;
+    }
+
     /**
      * Whether activity is eligible for activity "refresh" after camera compat force rotation
      * treatment. See {@link DisplayRotationCompatPolicy} for context.
@@ -313,7 +514,7 @@
         return shouldEnableWithOptOutOverrideAndProperty(
                 /* gatingCondition */ () -> mLetterboxConfiguration
                         .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
-                OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH,
+                mIsOverrideCameraCompatDisableRefreshEnabled,
                 mBooleanPropertyCameraCompatAllowRefresh);
     }
 
@@ -335,7 +536,7 @@
         return shouldEnableWithOverrideAndProperty(
                 /* gatingCondition */ () -> mLetterboxConfiguration
                         .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
-                OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
+                mIsOverrideCameraCompatEnableRefreshViaPauseEnabled,
                 mBooleanPropertyCameraCompatEnableRefreshViaPause);
     }
 
@@ -354,15 +555,19 @@
         return shouldEnableWithOptOutOverrideAndProperty(
                 /* gatingCondition */ () -> mLetterboxConfiguration
                         .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
-                OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION,
+                mIsOverrideCameraCompatDisableForceRotationEnabled,
                 mBooleanPropertyCameraCompatAllowForceRotation);
     }
 
+    private boolean isCompatChangeEnabled(long overrideChangeId) {
+        return mActivityRecord.info.isChangeEnabled(overrideChangeId);
+    }
+
     /**
      * Returns {@code true} when the following conditions are met:
      * <ul>
      *     <li>{@code gatingCondition} isn't {@code false}
-     *     <li>OEM didn't opt out with a {@code overrideChangeId} override
+     *     <li>OEM didn't opt out with a per-app override
      *     <li>App developers didn't opt out with a component {@code property}
      * </ul>
      *
@@ -370,12 +575,30 @@
      * disabled on per-app basis by OEMs or app developers.
      */
     private boolean shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition,
-            long overrideChangeId, Boolean property) {
+            boolean isOverrideChangeEnabled, Boolean property) {
         if (!gatingCondition.getAsBoolean()) {
             return false;
         }
-        return !Boolean.FALSE.equals(property)
-                && !mActivityRecord.info.isChangeEnabled(overrideChangeId);
+        return !FALSE.equals(property) && !isOverrideChangeEnabled;
+    }
+
+    /**
+     * Returns {@code true} when the following conditions are met:
+     * <ul>
+     *     <li>{@code gatingCondition} isn't {@code false}
+     *     <li>OEM did opt in with a per-app override
+     *     <li>App developers didn't opt out with a component {@code property}
+     * </ul>
+     *
+     * <p>This is used for the treatments that are enabled based with the heuristic but can be
+     * disabled on per-app basis by OEMs or app developers.
+     */
+    private boolean shouldEnableWithOptInOverrideAndOptOutProperty(BooleanSupplier gatingCondition,
+            boolean isOverrideChangeEnabled, Boolean property) {
+        if (!gatingCondition.getAsBoolean()) {
+            return false;
+        }
+        return !FALSE.equals(property) && isOverrideChangeEnabled;
     }
 
     /**
@@ -384,21 +607,20 @@
      *     <li>{@code gatingCondition} isn't {@code false}
      *     <li>App developers didn't opt out with a component {@code property}
      *     <li>App developers opted in with a component {@code property} or an OEM opted in with a
-     *     component {@code property}
+     *     per-app override
      * </ul>
      *
      * <p>This is used for the treatments that are enabled only on per-app basis.
      */
     private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition,
-            long overrideChangeId, Boolean property) {
+            boolean isOverrideChangeEnabled, Boolean property) {
         if (!gatingCondition.getAsBoolean()) {
             return false;
         }
-        if (Boolean.FALSE.equals(property)) {
+        if (FALSE.equals(property)) {
             return false;
         }
-        return Boolean.TRUE.equals(property)
-                || mActivityRecord.info.isChangeEnabled(overrideChangeId);
+        return TRUE.equals(property) || isOverrideChangeEnabled;
     }
 
     boolean hasWallpaperBackgroundForLetterbox() {
@@ -422,7 +644,7 @@
             if (w == null) {
                 return;
             }
-            adjustBoundsForTaskbar(w, outBounds);
+            adjustBoundsIfNeeded(w, outBounds);
         } else {
             outBounds.setEmpty();
         }
@@ -465,13 +687,13 @@
         if (w == null || winHint != null && w != winHint) {
             return;
         }
-        updateRoundedCorners(w);
+        updateRoundedCornersIfNeeded(w);
         // If there is another main window that is not an application-starting window, we should
         // update rounded corners for it as well, to avoid flickering rounded corners.
         final WindowState nonStartingAppW = mActivityRecord.findMainWindow(
                 /* includeStartingApp= */ false);
         if (nonStartingAppW != null && nonStartingAppW != w) {
-            updateRoundedCorners(nonStartingAppW);
+            updateRoundedCornersIfNeeded(nonStartingAppW);
         }
 
         updateWallpaperForLetterbox(w);
@@ -533,7 +755,7 @@
     // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
     // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
     // actually fullscreen.
-    private boolean isDisplayFullScreenAndInPosture(DeviceStateController.FoldState state,
+    private boolean isDisplayFullScreenAndInPosture(DeviceStateController.DeviceState state,
             boolean isTabletop) {
         Task task = mActivityRecord.getTask();
         return mActivityRecord.mDisplayContent != null
@@ -559,7 +781,7 @@
         // Don't check resolved configuration because it may not be updated yet during
         // configuration change.
         boolean bookMode = isDisplayFullScreenAndInPosture(
-                DeviceStateController.FoldState.HALF_FOLDED, false /* isTabletop */);
+                DeviceStateController.DeviceState.HALF_FOLDED, false /* isTabletop */);
         return isHorizontalReachabilityEnabled(parentConfiguration)
                 // Using the last global dynamic position to avoid "jumps" when moving
                 // between apps or activities.
@@ -571,7 +793,7 @@
         // Don't check resolved configuration because it may not be updated yet during
         // configuration change.
         boolean tabletopMode = isDisplayFullScreenAndInPosture(
-                DeviceStateController.FoldState.HALF_FOLDED, true /* isTabletop */);
+                DeviceStateController.DeviceState.HALF_FOLDED, true /* isTabletop */);
         return isVerticalReachabilityEnabled(parentConfiguration)
                 // Using the last global dynamic position to avoid "jumps" when moving
                 // between apps or activities.
@@ -584,7 +806,7 @@
                 ? getSplitScreenAspectRatio()
                 : mActivityRecord.shouldCreateCompatDisplayInsets()
                     ? getDefaultMinAspectRatioForUnresizableApps()
-                    : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+                    : getDefaultMinAspectRatio();
     }
 
     private float getDefaultMinAspectRatioForUnresizableApps() {
@@ -593,7 +815,7 @@
             return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
                     > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
                             ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
-                            : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+                            : getDefaultMinAspectRatio();
         }
 
         return getSplitScreenAspectRatio();
@@ -621,6 +843,16 @@
         return computeAspectRatio(bounds);
     }
 
+    private float getDefaultMinAspectRatio() {
+        final DisplayContent displayContent = mActivityRecord.getDisplayContent();
+        if (displayContent == null
+                || !mLetterboxConfiguration
+                    .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
+            return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+        }
+        return computeAspectRatio(new Rect(displayContent.getBounds()));
+    }
+
     Resources getResources() {
         return mActivityRecord.mWmService.mContext.getResources();
     }
@@ -714,6 +946,7 @@
      *   <li>Activity is portrait-only.
      *   <li>Fullscreen window in landscape device orientation.
      *   <li>Horizontal Reachability is enabled.
+     *   <li>Activity fills parent vertically.
      * </ul>
      */
     private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) {
@@ -721,10 +954,14 @@
                 && parentConfiguration.windowConfiguration.getWindowingMode()
                         == WINDOWING_MODE_FULLSCREEN
                 && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
-                && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT);
+                        && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT)
+                // Check whether the activity fills the parent vertically.
+                && parentConfiguration.windowConfiguration.getBounds().height()
+                        == mActivityRecord.getBounds().height();
     }
 
-    private boolean isHorizontalReachabilityEnabled() {
+    @VisibleForTesting
+    boolean isHorizontalReachabilityEnabled() {
         return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
     }
 
@@ -736,6 +973,7 @@
      *   <li>Activity is landscape-only.
      *   <li>Fullscreen window in portrait device orientation.
      *   <li>Vertical Reachability is enabled.
+     *   <li>Activity fills parent horizontally.
      * </ul>
      */
     private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) {
@@ -743,10 +981,14 @@
                 && parentConfiguration.windowConfiguration.getWindowingMode()
                         == WINDOWING_MODE_FULLSCREEN
                 && (parentConfiguration.orientation == ORIENTATION_PORTRAIT
-                && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE);
+                        && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE)
+                // Check whether the activity fills the parent horizontally.
+                && parentConfiguration.windowConfiguration.getBounds().width()
+                        == mActivityRecord.getBounds().width();
     }
 
-    private boolean isVerticalReachabilityEnabled() {
+    @VisibleForTesting
+    boolean isVerticalReachabilityEnabled() {
         return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
     }
 
@@ -755,8 +997,8 @@
         return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
                 // Check for FLAG_SHOW_WALLPAPER explicitly instead of using
                 // WindowContainer#showWallpaper because the later will return true when this
-                // activity is using blurred wallpaper for letterbox backgroud.
-                && (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0;
+                // activity is using blurred wallpaper for letterbox background.
+                && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0;
     }
 
     @VisibleForTesting
@@ -808,106 +1050,107 @@
         return mLetterboxConfiguration.getLetterboxBackgroundColor();
     }
 
-    private void updateRoundedCorners(WindowState mainWindow) {
+    private void updateRoundedCornersIfNeeded(final WindowState mainWindow) {
         final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
-        if (windowSurface != null && windowSurface.isValid()) {
-            final Transaction transaction = mActivityRecord.getSyncTransaction();
-
-            if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
-                // We don't want corner radius on the window.
-                // In the case the ActivityRecord requires a letterboxed animation we never want
-                // rounded corners on the window because rounded corners are applied at the
-                // animation-bounds surface level and rounded corners on the window would interfere
-                // with that leading to unexpected rounded corner positioning during the animation.
-                transaction
-                        .setWindowCrop(windowSurface, null)
-                        .setCornerRadius(windowSurface, 0);
-                return;
-            }
-
-            Rect cropBounds = null;
-
-            if (hasVisibleTaskbar(mainWindow)) {
-                cropBounds = new Rect(mActivityRecord.getBounds());
-
-                // Rounded corners should be displayed above the taskbar.
-                // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo
-                // because taskbar bounds are in screen coordinates
-                adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
-
-                // Activity bounds are in screen coordinates while (0,0) for activity's surface
-                // control is at the top left corner of an app window so offsetting bounds
-                // accordingly.
-                cropBounds.offsetTo(0, 0);
-            }
-
-            transaction
-                    .setWindowCrop(windowSurface, cropBounds)
-                    .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
+        if (windowSurface == null || !windowSurface.isValid()) {
+            return;
         }
+
+        // cropBounds must be non-null for the cornerRadius to be ever applied.
+        mActivityRecord.getSyncTransaction()
+                .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow))
+                .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
     }
 
-    private boolean requiresRoundedCorners(WindowState mainWindow) {
-        final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
+    @VisibleForTesting
+    @Nullable
+    Rect getCropBoundsIfNeeded(final WindowState mainWindow) {
+        if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+            // We don't want corner radius on the window.
+            // In the case the ActivityRecord requires a letterboxed animation we never want
+            // rounded corners on the window because rounded corners are applied at the
+            // animation-bounds surface level and rounded corners on the window would interfere
+            // with that leading to unexpected rounded corner positioning during the animation.
+            return null;
+        }
 
+        final Rect cropBounds = new Rect(mActivityRecord.getBounds());
+
+        // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo}
+        // because taskbar bounds used in {@link #adjustBoundsIfNeeded}
+        // are in screen coordinates
+        adjustBoundsIfNeeded(mainWindow, cropBounds);
+
+        // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface
+        // control is in the top left corner of an app window so offsetting bounds
+        // accordingly.
+        cropBounds.offsetTo(0, 0);
+        return cropBounds;
+    }
+
+    private boolean requiresRoundedCorners(final WindowState mainWindow) {
         return isLetterboxedNotForDisplayCutout(mainWindow)
-                && mLetterboxConfiguration.isLetterboxActivityCornersRounded()
-                && taskbarInsetsSource != null;
+                && mLetterboxConfiguration.isLetterboxActivityCornersRounded();
     }
 
     // Returns rounded corners radius the letterboxed activity should have based on override in
     // R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii.
-    // Device corners can be different on the right and left sides but we use the same radius
+    // Device corners can be different on the right and left sides, but we use the same radius
     // for all corners for consistency and pick a minimal bottom one for consistency with a
     // taskbar rounded corners.
-    int getRoundedCornersRadius(WindowState mainWindow) {
-        if (!requiresRoundedCorners(mainWindow)) {
+    int getRoundedCornersRadius(final WindowState mainWindow) {
+        if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
             return 0;
         }
 
+        final int radius;
         if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) {
-            return mLetterboxConfiguration.getLetterboxActivityCornersRadius();
+            radius = mLetterboxConfiguration.getLetterboxActivityCornersRadius();
+        } else {
+            final InsetsState insetsState = mainWindow.getInsetsState();
+            radius = Math.min(
+                    getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
+                    getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
         }
 
-        final InsetsState insetsState = mainWindow.getInsetsState();
-        return Math.min(
-                getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
-                getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
+        final float scale = mainWindow.mInvGlobalScale;
+        return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius;
     }
 
     /**
-     * Returns whether the taskbar is visible. Returns false if the window is in immersive mode,
-     * since the user can swipe to show/hide the taskbar as an overlay.
+     * Returns the taskbar in case it is visible and expanded in height, otherwise returns null.
      */
-    private boolean hasVisibleTaskbar(WindowState mainWindow) {
-        final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
-
-        return taskbarInsetsSource != null
-                && taskbarInsetsSource.isVisible();
+    @VisibleForTesting
+    @Nullable
+    InsetsSource getExpandedTaskbarOrNull(final WindowState mainWindow) {
+        final InsetsSource taskbar = mainWindow.getInsetsState().peekSource(
+                InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        if (taskbar != null && taskbar.isVisible()
+                && taskbar.getFrame().height() >= mExpandedTaskBarHeight) {
+            return taskbar;
+        }
+        return null;
     }
 
-    private InsetsSource getTaskbarInsetsSource(WindowState mainWindow) {
-        final InsetsState insetsState = mainWindow.getInsetsState();
-        return insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
-    }
-
-    private void adjustBoundsForTaskbar(WindowState mainWindow, Rect bounds) {
+    private void adjustBoundsIfNeeded(final WindowState mainWindow, final Rect bounds) {
         // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
         // an insets frame is equal to a navigation bar which shouldn't affect position of
         // rounded corners since apps are expected to handle navigation bar inset.
         // This condition checks whether the taskbar is visible.
         // Do not crop the taskbar inset if the window is in immersive mode - the user can
         // swipe to show/hide the taskbar as an overlay.
-        if (hasVisibleTaskbar(mainWindow)) {
-            adjustBoundsForTaskbarUnchecked(mainWindow, bounds);
+        // Adjust the bounds only in case there is an expanded taskbar,
+        // otherwise the rounded corners will be shown behind the navbar.
+        final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow);
+        if (expandedTaskbarOrNull != null) {
+            // Rounded corners should be displayed above the expanded taskbar.
+            bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top);
         }
-    }
 
-    private void adjustBoundsForTaskbarUnchecked(WindowState mainWindow, Rect bounds) {
-        // Rounded corners should be displayed above the taskbar.
-        bounds.bottom =
-                Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
-        scaleIfNeeded(bounds);
+        final float scale = mainWindow.mInvGlobalScale;
+        if (scale != 1f && scale > 0f) {
+            bounds.scale(scale);
+        }
     }
 
     private int getInsetsStateCornerRadius(
@@ -1014,6 +1257,9 @@
                 + mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps());
         pw.println(prefix + "  isSplitScreenAspectRatioForUnresizableAppsEnabled="
                 + mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
+        pw.println(prefix + "  isDisplayAspectRatioEnabledForFixedOrientationLetterbox="
+                + mLetterboxConfiguration
+                .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox());
     }
 
     /**
@@ -1074,7 +1320,7 @@
             int letterboxPositionForHorizontalReachability = getLetterboxConfiguration()
                     .getLetterboxPositionForHorizontalReachability(
                             isDisplayFullScreenAndInPosture(
-                                    DeviceStateController.FoldState.HALF_FOLDED,
+                                    DeviceStateController.DeviceState.HALF_FOLDED,
                                     false /* isTabletop */));
             positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
                     letterboxPositionForHorizontalReachability);
@@ -1082,7 +1328,7 @@
             int letterboxPositionForVerticalReachability = getLetterboxConfiguration()
                     .getLetterboxPositionForVerticalReachability(
                             isDisplayFullScreenAndInPosture(
-                                    DeviceStateController.FoldState.HALF_FOLDED,
+                                    DeviceStateController.DeviceState.HALF_FOLDED,
                                     true /* isTabletop */));
             positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
                     letterboxPositionForVerticalReachability);
@@ -1191,7 +1437,8 @@
         // To avoid wrong behaviour, we're not forcing orientation for activities with not
         // fixed orientation (e.g. permission dialogs).
         return hasInheritedLetterboxBehavior()
-                && mActivityRecord.mOrientation != SCREEN_ORIENTATION_UNSPECIFIED;
+                && mActivityRecord.getOverrideOrientation()
+                        != SCREEN_ORIENTATION_UNSPECIFIED;
     }
 
     float getInheritedMinAspectRatio() {
@@ -1246,20 +1493,4 @@
         mInheritedSizeCompatScale = 1f;
         mInheritedCompatDisplayInsets = null;
     }
-
-    private void scaleIfNeeded(Rect bounds) {
-        if (boundsNeedToScale()) {
-            bounds.scale(1.0f / mActivityRecord.getCompatScale());
-        }
-    }
-
-    private boolean boundsNeedToScale() {
-        if (hasInheritedLetterboxBehavior()) {
-            return mIsInheritedInSizeCompatMode
-                    && mInheritedSizeCompatScale < 1.0f;
-        } else {
-            return mActivityRecord.inSizeCompatMode()
-                    && mActivityRecord.getCompatScale() < 1.0f;
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 30bdc34..2edb082 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -51,10 +51,10 @@
     /**
      *   Called by the DeviceStateManager callback when the state changes.
      */
-    void foldStateChanged(DeviceStateController.FoldState newFoldState) {
+    void foldStateChanged(DeviceStateController.DeviceState newDeviceState) {
         // Ignore transitions to/from half-folded.
-        if (newFoldState == DeviceStateController.FoldState.HALF_FOLDED) return;
-        mIsFolded = newFoldState == DeviceStateController.FoldState.FOLDED;
+        if (newDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) return;
+        mIsFolded = newDeviceState == DeviceStateController.DeviceState.FOLDED;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index f3713eb..6b3c533 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -53,6 +53,8 @@
         }
     }
 
+    private final DisplayInfo mDisplayInfo;
+    private final Mode mDefaultMode;
     private final Mode mLowRefreshRateMode;
     private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
     private final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -83,7 +85,9 @@
 
     RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
             HighRefreshRateDenylist denylist) {
-        mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
+        mDisplayInfo = displayInfo;
+        mDefaultMode = displayInfo.getDefaultMode();
+        mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
         mHighRefreshRateDenylist = denylist;
         mWmService = wmService;
     }
@@ -92,10 +96,9 @@
      * Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
      * default mode.
      */
-    private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
-        Mode mode = displayInfo.getDefaultMode();
+    private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
         float[] refreshRates = displayInfo.getDefaultRefreshRates();
-        float bestRefreshRate = mode.getRefreshRate();
+        float bestRefreshRate = defaultMode.getRefreshRate();
         mMinSupportedRefreshRate = bestRefreshRate;
         mMaxSupportedRefreshRate = bestRefreshRate;
         for (int i = refreshRates.length - 1; i >= 0; i--) {
@@ -121,13 +124,39 @@
     }
 
     int getPreferredModeId(WindowState w) {
-        // If app is animating, it's not able to control refresh rate because we want the animation
-        // to run in default refresh rate.
-        if (w.isAnimating(TRANSITION | PARENTS)) {
+        final int preferredDisplayModeId = w.mAttrs.preferredDisplayModeId;
+        if (preferredDisplayModeId <= 0) {
+            // Unspecified, use default mode.
             return 0;
         }
 
-        return w.mAttrs.preferredDisplayModeId;
+        // If app is animating, it's not able to control refresh rate because we want the animation
+        // to run in default refresh rate. But if the display size of default mode is different
+        // from the using preferred mode, then still keep the preferred mode to avoid disturbing
+        // the animation.
+        if (w.isAnimating(TRANSITION | PARENTS)) {
+            Display.Mode preferredMode = null;
+            for (Display.Mode mode : mDisplayInfo.supportedModes) {
+                if (preferredDisplayModeId == mode.getModeId()) {
+                    preferredMode = mode;
+                    break;
+                }
+            }
+            if (preferredMode != null) {
+                final int pW = preferredMode.getPhysicalWidth();
+                final int pH = preferredMode.getPhysicalHeight();
+                if ((pW != mDefaultMode.getPhysicalWidth()
+                        || pH != mDefaultMode.getPhysicalHeight())
+                        && pW == mDisplayInfo.getNaturalWidth()
+                        && pH == mDisplayInfo.getNaturalHeight()) {
+                    // Prefer not to change display size when animating.
+                    return preferredDisplayModeId;
+                }
+            }
+            return 0;
+        }
+
+        return preferredDisplayModeId;
     }
 
     /**
@@ -165,12 +194,9 @@
         // of that mode id.
         final int preferredModeId = w.mAttrs.preferredDisplayModeId;
         if (preferredModeId > 0) {
-            DisplayInfo info = w.getDisplayInfo();
-            if (info != null) {
-                for (Display.Mode mode : info.supportedModes) {
-                    if (preferredModeId == mode.getModeId()) {
-                        return mode.getRefreshRate();
-                    }
+            for (Display.Mode mode : mDisplayInfo.supportedModes) {
+                if (preferredModeId == mode.getModeId()) {
+                    return mode.getRefreshRate();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index fd8b614..ef45c22 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -800,6 +800,10 @@
                 if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
                     // It also invokes kill().
                     mDisplayContent.setRotationAnimation(null);
+                    if (mDisplayContent.mDisplayRotationCompatPolicy != null) {
+                        mDisplayContent.mDisplayRotationCompatPolicy
+                                .onScreenRotationAnimationFinished();
+                    }
                 } else {
                     kill();
                 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index beaac05..3aa80c4 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3327,12 +3327,17 @@
         // We intend to let organizer manage task visibility but it doesn't
         // have enough information until we finish shell transitions.
         // In the mean time we do an easy fix here.
-        final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS | CHILDREN);
+        final boolean visible = isVisible();
+        final boolean show = visible || isAnimating(TRANSITION | PARENTS | CHILDREN);
         if (mSurfaceControl != null) {
             if (show != mLastSurfaceShowing) {
                 t.setVisibility(mSurfaceControl, show);
             }
         }
+        // Only show the overlay if the task has other visible children
+        if (mOverlayHost != null) {
+            mOverlayHost.setVisibility(t, visible);
+        }
         mLastSurfaceShowing = show;
     }
 
diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
index 975b21c..88c410b 100644
--- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java
+++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java
@@ -80,6 +80,12 @@
         }
     }
 
+    void setVisibility(SurfaceControl.Transaction t, boolean visible) {
+        if (mSurfaceControl != null) {
+            t.setVisibility(mSurfaceControl, visible);
+        }
+    }
+
     void addOverlay(SurfaceControlViewHost.SurfacePackage p, SurfaceControl currentParent) {
         requireOverlaySurfaceControl();
         mOverlays.add(p);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8bdab9c..9a20354 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -73,6 +73,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.Point;
@@ -178,8 +179,9 @@
     protected final WindowList<E> mChildren = new WindowList<E>();
 
     // The specified orientation for this window container.
-    @ActivityInfo.ScreenOrientation
-    protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+    // Shouldn't be accessed directly since subclasses can override getOverrideOrientation.
+    @ScreenOrientation
+    private int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
 
     /**
      * The window container which decides its orientation since the last time
@@ -1427,19 +1429,20 @@
 
     /**
      * Gets the configuration orientation by the requested screen orientation
-     * ({@link ActivityInfo.ScreenOrientation}) of this activity.
+     * ({@link ScreenOrientation}) of this activity.
      *
      * @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE},
      *         {@link Configuration#ORIENTATION_PORTRAIT},
      *         {@link Configuration#ORIENTATION_UNDEFINED}).
      */
+    @ScreenOrientation
     int getRequestedConfigurationOrientation() {
         return getRequestedConfigurationOrientation(false /* forDisplay */);
     }
 
     /**
      * Gets the configuration orientation by the requested screen orientation
-     * ({@link ActivityInfo.ScreenOrientation}) of this activity.
+     * ({@link ScreenOrientation}) of this activity.
      *
      * @param forDisplay whether it is the requested config orientation for display.
      *                   If {@code true}, we may reverse the requested orientation if the root is
@@ -1450,8 +1453,9 @@
      *         {@link Configuration#ORIENTATION_PORTRAIT},
      *         {@link Configuration#ORIENTATION_UNDEFINED}).
      */
+    @ScreenOrientation
     int getRequestedConfigurationOrientation(boolean forDisplay) {
-        int requestedOrientation = mOrientation;
+        int requestedOrientation = getOverrideOrientation();
         final RootDisplayArea root = getRootDisplayArea();
         if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) {
             // Reverse the requested orientation if the orientation of its root is different from
@@ -1461,7 +1465,7 @@
             // (portrait).
             // When an app below the DAG is requesting landscape, it should actually request the
             // display to be portrait, so that the DAG and the app will be in landscape.
-            requestedOrientation = reverseOrientation(mOrientation);
+            requestedOrientation = reverseOrientation(getOverrideOrientation());
         }
 
         if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
@@ -1486,7 +1490,7 @@
      *
      * @param orientation the specified orientation.
      */
-    void setOrientation(int orientation) {
+    void setOrientation(@ScreenOrientation int orientation) {
         setOrientation(orientation, null /* requestingContainer */);
     }
 
@@ -1494,17 +1498,17 @@
      * Sets the specified orientation of this container. It percolates this change upward along the
      * hierarchy to let each level of the hierarchy a chance to respond to it.
      *
-     * @param orientation the specified orientation. Needs to be one of {@link
-     *      android.content.pm.ActivityInfo.ScreenOrientation}.
+     * @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}.
      * @param requestingContainer the container which orientation request has changed. Mostly used
      *                            to ensure it gets correct configuration.
      */
-    void setOrientation(int orientation, @Nullable WindowContainer requestingContainer) {
-        if (mOrientation == orientation) {
+    void setOrientation(@ScreenOrientation int orientation,
+            @Nullable WindowContainer requestingContainer) {
+        if (getOverrideOrientation() == orientation) {
             return;
         }
 
-        mOrientation = orientation;
+        setOverrideOrientation(orientation);
         final WindowContainer parent = getParent();
         if (parent != null) {
             if (getConfiguration().orientation != getRequestedConfigurationOrientation()
@@ -1523,9 +1527,9 @@
         }
     }
 
-    @ActivityInfo.ScreenOrientation
+    @ScreenOrientation
     int getOrientation() {
-        return getOrientation(mOrientation);
+        return getOrientation(getOverrideOrientation());
     }
 
     /**
@@ -1539,7 +1543,8 @@
      *                  better match.
      * @return The orientation as specified by this branch or the window hierarchy.
      */
-    int getOrientation(int candidate) {
+    @ScreenOrientation
+    int getOrientation(@ScreenOrientation int candidate) {
         mLastOrientationSource = null;
         if (!providesOrientation()) {
             return SCREEN_ORIENTATION_UNSET;
@@ -1549,16 +1554,16 @@
         // specified; otherwise we prefer to use the orientation of its topmost child that has one
         // specified and fall back on this container's unset or unspecified value as a candidate
         // if none of the children have a better candidate for the orientation.
-        if (mOrientation != SCREEN_ORIENTATION_UNSET
-                && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+        if (getOverrideOrientation() != SCREEN_ORIENTATION_UNSET
+                && getOverrideOrientation() != SCREEN_ORIENTATION_UNSPECIFIED) {
             mLastOrientationSource = this;
-            return mOrientation;
+            return getOverrideOrientation();
         }
 
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
 
-            // TODO: Maybe mOrientation should default to SCREEN_ORIENTATION_UNSET vs.
+            // TODO: Maybe mOverrideOrientation should default to SCREEN_ORIENTATION_UNSET vs.
             // SCREEN_ORIENTATION_UNSPECIFIED?
             final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND
                     ? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET);
@@ -1590,6 +1595,20 @@
     }
 
     /**
+     * Returns orientation specified on this level of hierarchy without taking children into
+     * account, like {@link #getOrientation} does, allowing subclasses to override. See {@link
+     * ActivityRecord#getOverrideOrientation} for an example.
+     */
+    @ScreenOrientation
+    protected int getOverrideOrientation() {
+        return mOverrideOrientation;
+    }
+
+    protected void setOverrideOrientation(@ScreenOrientation int orientation) {
+        mOverrideOrientation = orientation;
+    }
+
+    /**
      * @return The deepest source which decides the orientation of this window container since the
      *         last time {@link #getOrientation(int) was called.
      */
@@ -2635,7 +2654,7 @@
 
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, CONFIGURATION_CONTAINER, logLevel);
-        proto.write(ORIENTATION, mOrientation);
+        proto.write(ORIENTATION, mOverrideOrientation);
         proto.write(VISIBLE, isVisible);
         writeIdentifierToProto(proto, IDENTIFIER);
         if (mSurfaceAnimator.isAnimating()) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4e32a7c..8931d80 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -113,6 +113,7 @@
 import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
+import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
 import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
@@ -167,7 +168,6 @@
 import android.app.IAssistDataReceiver;
 import android.app.WindowConfiguration;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -177,7 +177,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.TestUtilityService;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -357,6 +356,7 @@
 public class WindowManagerService extends IWindowManager.Stub
         implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM;
+    private static final int TRACE_MAX_SECTION_NAME_LENGTH = 127;
 
     static final int LAYOUT_REPEAT_THRESHOLD = 4;
 
@@ -4172,7 +4172,8 @@
      * <p>Note: this assumes that {@link #mGlobalLock} is held by the caller.
      */
     boolean isIgnoreOrientationRequestDisabled() {
-        return mIsIgnoreOrientationRequestDisabled;
+        return mIsIgnoreOrientationRequestDisabled
+                || !mLetterboxConfiguration.isIgnoreOrientationRequestAllowed();
     }
 
     @Override
@@ -5443,10 +5444,15 @@
 
                 case WAITING_FOR_DRAWN_TIMEOUT: {
                     Runnable callback = null;
-                    final WindowContainer container = (WindowContainer) msg.obj;
+                    final WindowContainer<?> container = (WindowContainer<?>) msg.obj;
                     synchronized (mGlobalLock) {
                         ProtoLog.w(WM_ERROR, "Timeout waiting for drawn: undrawn=%s",
                                 container.mWaitingForDrawn);
+                        if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                            for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
+                                traceEndWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
+                            }
+                        }
                         container.mWaitingForDrawn.clear();
                         callback = mWaitingForDrawnCallbacks.remove(container);
                     }
@@ -5839,6 +5845,11 @@
             if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
                 return displayContent.mInitialDisplayDensity;
             }
+
+            DisplayInfo info = mDisplayManagerInternal.getDisplayInfo(displayId);
+            if (info != null && info.hasAccess(Binder.getCallingUid())) {
+                return info.logicalDensityDpi;
+            }
         }
         return -1;
     }
@@ -5870,6 +5881,11 @@
                 final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
                 if (displayContent != null) {
                     displayContent.setForcedDensity(density, targetUserId);
+                } else {
+                    DisplayInfo info = mDisplayManagerInternal.getDisplayInfo(displayId);
+                    if (info != null) {
+                        mDisplayWindowSettings.setForcedDensity(info, density, userId);
+                    }
                 }
             }
         } finally {
@@ -5894,6 +5910,12 @@
                 if (displayContent != null) {
                     displayContent.setForcedDensity(displayContent.mInitialDisplayDensity,
                             callingUserId);
+                } else {
+                    DisplayInfo info = mDisplayManagerInternal.getDisplayInfo(displayId);
+                    if (info != null) {
+                        mDisplayWindowSettings.setForcedDensity(info, info.logicalDensityDpi,
+                                userId);
+                    }
                 }
             }
         } finally {
@@ -6037,10 +6059,16 @@
                     // Window has been removed or hidden; no draw will now happen, so stop waiting.
                     ProtoLog.w(WM_DEBUG_SCREEN_ON, "Aborted waiting for drawn: %s", win);
                     container.mWaitingForDrawn.remove(win);
+                    if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                        traceEndWaitingForWindowDrawn(win);
+                    }
                 } else if (win.hasDrawn()) {
                     // Window is now drawn (and shown).
                     ProtoLog.d(WM_DEBUG_SCREEN_ON, "Window drawn win=%s", win);
                     container.mWaitingForDrawn.remove(win);
+                    if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                        traceEndWaitingForWindowDrawn(win);
+                    }
                 }
             }
             if (container.mWaitingForDrawn.isEmpty()) {
@@ -6051,6 +6079,22 @@
         });
     }
 
+    private void traceStartWaitingForWindowDrawn(WindowState window) {
+        final String traceName = TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD + "#"
+                + window.getWindowTag();
+        final String shortenedTraceName = traceName.substring(0, Math.min(
+                TRACE_MAX_SECTION_NAME_LENGTH, traceName.length()));
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, shortenedTraceName, /* cookie= */ 0);
+    }
+
+    private void traceEndWaitingForWindowDrawn(WindowState window) {
+        final String traceName = TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD + "#"
+                + window.getWindowTag();
+        final String shortenedTraceName = traceName.substring(0, Math.min(
+                TRACE_MAX_SECTION_NAME_LENGTH, traceName.length()));
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, shortenedTraceName, /* cookie= */ 0);
+    }
+
     void requestTraversal() {
         mWindowPlacerLocked.requestTraversal();
     }
@@ -7785,7 +7829,7 @@
 
         @Override
         public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) {
-            final WindowContainer container = displayId == INVALID_DISPLAY
+            final WindowContainer<?> container = displayId == INVALID_DISPLAY
                     ? mRoot : mRoot.getDisplayContent(displayId);
             if (container == null) {
                 // The waiting container doesn't exist, no need to wait to run the callback. Run and
@@ -7801,6 +7845,12 @@
                 if (container.mWaitingForDrawn.isEmpty()) {
                     allWindowsDrawn = true;
                 } else {
+                    if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+                        for (int i = 0; i < container.mWaitingForDrawn.size(); i++) {
+                            traceStartWaitingForWindowDrawn(container.mWaitingForDrawn.get(i));
+                        }
+                    }
+
                     mWaitingForDrawnCallbacks.put(container, callback);
                     mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout);
                     checkDrawnWindowsLocked();
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index aef6d1d..a06d84c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -955,6 +955,10 @@
                     runSetBooleanFlag(pw, mLetterboxConfiguration
                             ::setIsSplitScreenAspectRatioForUnresizableAppsEnabled);
                     break;
+                case "--isDisplayAspectRatioEnabledForFixedOrientationLetterbox":
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox);
+                    break;
                 case "--isTranslucentLetterboxingEnabled":
                     runSetBooleanFlag(pw, mLetterboxConfiguration
                             ::setTranslucentLetterboxingOverrideEnabled);
@@ -1030,6 +1034,10 @@
                         mLetterboxConfiguration
                                 .resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
                         break;
+                    case "IsDisplayAspectRatioEnabledForFixedOrientationLetterbox":
+                        mLetterboxConfiguration
+                                .resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
+                        break;
                     case "isTranslucentLetterboxingEnabled":
                         mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
                         break;
@@ -1140,6 +1148,7 @@
             mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
             mLetterboxConfiguration.resetIsEducationEnabled();
             mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+            mLetterboxConfiguration.resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
             mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
             mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
             mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
@@ -1187,7 +1196,9 @@
             pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
                     + mLetterboxConfiguration
                             .getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
-
+            pw.println("Is using display aspect ratio as aspect ratio for all letterboxed apps: "
+                    + mLetterboxConfiguration
+                            .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox());
             pw.println("    Is activity \"refresh\" in camera compatibility treatment enabled: "
                     + mLetterboxConfiguration.isCameraCompatRefreshEnabled());
             pw.println("    Refresh using \"stopped -> resumed\" cycle: "
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index 3e165e4..14c816d 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -88,14 +88,19 @@
 
     private final Object mLock = new Object();
 
+    @Surface.Rotation
+    private final int mDefaultRotation;
+
     /**
      * Creates a new WindowOrientationListener.
      *
      * @param context for the WindowOrientationListener.
      * @param handler Provides the Looper for receiving sensor updates.
+     * @param defaultRotation Default rotation of the display.
      */
-    public WindowOrientationListener(Context context, Handler handler) {
-        this(context, handler, SensorManager.SENSOR_DELAY_UI);
+    public WindowOrientationListener(Context context, Handler handler,
+            @Surface.Rotation int defaultRotation) {
+        this(context, handler, defaultRotation, SensorManager.SENSOR_DELAY_UI);
     }
 
     /**
@@ -103,7 +108,7 @@
      *
      * @param context for the WindowOrientationListener.
      * @param handler Provides the Looper for receiving sensor updates.
-     * @param wmService WindowManagerService to read the device config from.
+     * @param defaultRotation Default rotation of the display.
      * @param rate at which sensor events are processed (see also
      * {@link android.hardware.SensorManager SensorManager}). Use the default
      * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
@@ -111,10 +116,11 @@
      *
      * This constructor is private since no one uses it.
      */
-    private WindowOrientationListener(
-            Context context, Handler handler, int rate) {
+    private WindowOrientationListener(Context context, Handler handler,
+            @Surface.Rotation int defaultRotation, int rate) {
         mContext = context;
         mHandler = handler;
+        mDefaultRotation = defaultRotation;
         mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
         mRate = rate;
         List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
@@ -1159,7 +1165,7 @@
                                 "Reusing the last rotation resolution: " + mLastRotationResolution);
                         finalizeRotation(mLastRotationResolution);
                     } else {
-                        finalizeRotation(Surface.ROTATION_0);
+                        finalizeRotation(mDefaultRotation);
                     }
                     return;
                 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ae03fbb..45dacbb 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2387,7 +2387,11 @@
         // IME parent may failed to attach to the app during rotating the screen.
         // See DisplayContent#shouldImeAttachedToApp, DisplayContent#isImeControlledByApp
         if (windowConfigChanged) {
-            getDisplayContent().updateImeControlTarget();
+            // If the window was the IME layering target, updates the IME surface parent in case
+            // the IME surface may be wrongly positioned when the window configuration affects the
+            // IME surface association. (e.g. Attach IME surface on the display instead of the
+            // app when the app bounds being letterboxed.)
+            mDisplayContent.updateImeControlTarget(isImeLayeringTarget() /* updateImeParent */);
         }
     }
 
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 693f3a0..eff9e8d 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -271,22 +271,22 @@
     private ConversationChannel getConversationChannel(String packageName, int userId,
             String shortcutId, ConversationInfo conversationInfo) {
         ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
-        return getConversationChannel(shortcutInfo, conversationInfo);
+        return getConversationChannel(
+                shortcutInfo, conversationInfo, packageName, userId, shortcutId);
     }
 
     @Nullable
     private ConversationChannel getConversationChannel(ShortcutInfo shortcutInfo,
-            ConversationInfo conversationInfo) {
+            ConversationInfo conversationInfo, String packageName, int userId, String shortcutId) {
         if (conversationInfo == null || conversationInfo.isDemoted()) {
             return null;
         }
         if (shortcutInfo == null) {
-            Slog.e(TAG, " Shortcut no longer found");
+            Slog.e(TAG, "Shortcut no longer found");
+            mInjector.getBackgroundExecutor().execute(
+                    () -> removeConversations(packageName, userId, Set.of(shortcutId)));
             return null;
         }
-        String packageName = shortcutInfo.getPackage();
-        String shortcutId = shortcutInfo.getId();
-        int userId = shortcutInfo.getUserId();
         int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
         NotificationChannel parentChannel =
                 mNotificationManagerInternal.getNotificationChannel(packageName, uid,
@@ -1130,38 +1130,41 @@
         public void onShortcutsRemoved(@NonNull String packageName,
                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
             mInjector.getBackgroundExecutor().execute(() -> {
-                int uid = Process.INVALID_UID;
-                try {
-                    uid = mContext.getPackageManager().getPackageUidAsUser(
-                            packageName, user.getIdentifier());
-                } catch (PackageManager.NameNotFoundException e) {
-                    Slog.e(TAG, "Package not found: " + packageName, e);
-                }
-                PackageData packageData = getPackage(packageName, user.getIdentifier());
-                Set<String> shortcutIds = new HashSet<>();
+                HashSet<String> shortcutIds = new HashSet<>();
                 for (ShortcutInfo shortcutInfo : shortcuts) {
-                    if (packageData != null) {
-                        if (DEBUG) Log.d(TAG, "Deleting shortcut: " + shortcutInfo.getId());
-                        packageData.deleteDataForConversation(shortcutInfo.getId());
-                    }
                     shortcutIds.add(shortcutInfo.getId());
                 }
-                if (uid != Process.INVALID_UID) {
-                    mNotificationManagerInternal.onConversationRemoved(
-                            packageName, uid, shortcutIds);
-                }
+                removeConversations(packageName, user.getIdentifier(), shortcutIds);
             });
         }
     }
 
+    private void removeConversations(
+            @NonNull String packageName, @NonNull int userId, @NonNull Set<String> shortcutIds) {
+        PackageData packageData = getPackage(packageName, userId);
+        if (packageData != null) {
+            for (String shortcutId : shortcutIds) {
+                if (DEBUG) Log.d(TAG, "Deleting shortcut: " + shortcutId);
+                packageData.deleteDataForConversation(shortcutId);
+            }
+        }
+        try {
+            int uid = mContext.getPackageManager().getPackageUidAsUser(
+                    packageName, userId);
+            mNotificationManagerInternal.onConversationRemoved(packageName, uid, shortcutIds);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Package not found when removing conversation: " + packageName, e);
+        }
+    }
+
     /** Listener for the notifications and their settings changes. */
     private class NotificationListener extends NotificationListenerService {
 
         private final int mUserId;
 
-        // Conversation package name + shortcut ID -> Number of active notifications
+        // Conversation package name + shortcut ID -> Keys of active notifications
         @GuardedBy("this")
-        private final Map<Pair<String, String>, Integer> mActiveNotifCounts = new ArrayMap<>();
+        private final Map<Pair<String, String>, Set<String>> mActiveNotifKeys = new ArrayMap<>();
 
         private NotificationListener(int userId) {
             mUserId = userId;
@@ -1175,8 +1178,10 @@
             String shortcutId = sbn.getNotification().getShortcutId();
             PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
                 synchronized (this) {
-                    mActiveNotifCounts.merge(
-                            Pair.create(sbn.getPackageName(), shortcutId), 1, Integer::sum);
+                    Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent(
+                            Pair.create(sbn.getPackageName(), shortcutId),
+                            (unusedKey) -> new HashSet<>());
+                    notificationKeys.add(sbn.getKey());
                 }
             });
 
@@ -1215,12 +1220,12 @@
                 Pair<String, String> conversationKey =
                         Pair.create(sbn.getPackageName(), shortcutId);
                 synchronized (this) {
-                    int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
-                    if (count <= 0) {
-                        mActiveNotifCounts.remove(conversationKey);
+                    Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent(
+                            conversationKey, (unusedKey) -> new HashSet<>());
+                    notificationKeys.remove(sbn.getKey());
+                    if (notificationKeys.isEmpty()) {
+                        mActiveNotifKeys.remove(conversationKey);
                         cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS);
-                    } else {
-                        mActiveNotifCounts.put(conversationKey, count);
                     }
                 }
             });
@@ -1286,7 +1291,7 @@
         }
 
         synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
-            return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
+            return mActiveNotifKeys.containsKey(Pair.create(packageName, shortcutId));
         }
     }
 
@@ -1349,9 +1354,11 @@
     }
 
     private void updateConversationStoreThenNotifyListeners(ConversationStore cs,
-            ConversationInfo modifiedConv, ShortcutInfo shortcutInfo) {
+            ConversationInfo modifiedConv, @NonNull ShortcutInfo shortcutInfo) {
         cs.addOrUpdate(modifiedConv);
-        ConversationChannel channel = getConversationChannel(shortcutInfo, modifiedConv);
+        ConversationChannel channel = getConversationChannel(
+                shortcutInfo, modifiedConv, shortcutInfo.getPackage(), shortcutInfo.getUserId(),
+                shortcutInfo.getId());
         if (channel != null) {
             notifyConversationsListeners(Arrays.asList(channel));
         }
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 33ac735..ea0481e 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -43,6 +43,9 @@
     <!-- needed by TrustManagerServiceTest to access LockSettings' secure storage -->
     <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
 
+    <!-- needed by GameManagerServiceTest because GameManager creates a UidObserver -->
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+
     <application android:testOnly="true"
                  android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index fa4a9de..2d5f0b0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -29,13 +29,16 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
+import android.app.ActivityManager;
 import android.app.GameManager;
 import android.app.GameModeInfo;
 import android.app.GameState;
@@ -203,6 +206,23 @@
         LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
     }
 
+    private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
+            throws Exception {
+        reset(mMockPackageManager);
+        final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
+        gameApplicationInfo.category = category;
+        gameApplicationInfo.packageName = packageName;
+        final PackageInfo pi = new PackageInfo();
+        pi.packageName = packageName;
+        pi.applicationInfo = gameApplicationInfo;
+        final List<PackageInfo> packages = new ArrayList<>();
+        packages.add(pi);
+        when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
+            .thenReturn(packages);
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+            .thenReturn(gameApplicationInfo);
+    }
+
     @After
     public void tearDown() throws Exception {
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
@@ -1597,4 +1617,113 @@
                 ArgumentMatchers.eq(DEFAULT_PACKAGE_UID),
                 ArgumentMatchers.eq(0.0f));
     }
+
+    private GameManagerService createServiceAndStartUser(int userId) {
+        GameManagerService gameManagerService = new GameManagerService(mMockContext,
+                mTestLooper.getLooper());
+        startUser(gameManagerService, userId);
+        return gameManagerService;
+    }
+
+    @Test
+    public void testGamePowerMode_gamePackage() throws Exception {
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        String[] packages = {mPackageName};
+        when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+    }
+
+    @Test
+    public void testGamePowerMode_twoGames() throws Exception {
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        String[] packages1 = {mPackageName};
+        when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
+        String someGamePkg = "some.game";
+        String[] packages2 = {someGamePkg};
+        int somePackageId = DEFAULT_PACKAGE_UID + 1;
+        when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+        HashMap<Integer, Boolean> powerState = new HashMap<>();
+        doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1)))
+                .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean());
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        assertTrue(powerState.get(Mode.GAME));
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        assertTrue(powerState.get(Mode.GAME));
+        gameManagerService.mUidObserver.onUidStateChanged(
+                somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        assertFalse(powerState.get(Mode.GAME));
+    }
+
+    @Test
+    public void testGamePowerMode_twoGamesOverlap() throws Exception {
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        String[] packages1 = {mPackageName};
+        when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages1);
+        String someGamePkg = "some.game";
+        String[] packages2 = {someGamePkg};
+        int somePackageId = DEFAULT_PACKAGE_UID + 1;
+        when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+    }
+
+    @Test
+    public void testGamePowerMode_released() throws Exception {
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        String[] packages = {mPackageName};
+        when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+    }
+
+    @Test
+    public void testGamePowerMode_noPackage() throws Exception {
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        String[] packages = {};
+        when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+    }
+
+    @Test
+    public void testGamePowerMode_notAGamePackage() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        String[] packages = {"someapp"};
+        when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, true);
+    }
+
+    @Test
+    public void testGamePowerMode_notAGamePackageNotReleased() throws Exception {
+        mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+        GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+        String[] packages = {"someapp"};
+        when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0);
+        gameManagerService.mUidObserver.onUidStateChanged(
+                DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        verify(mMockPowerManager, times(0)).setPowerMode(Mode.GAME, false);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index e4f9eaf..9b23f8b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -1089,8 +1089,7 @@
             Consumer<Uri> consumer = invocation.getArgument(invocation.getArguments().length - 1);
             consumer.accept(Uri.parse("a/b.png"));
             return null;
-        }).when(mMockScreenshotHelper).provideScreenshot(
-                any(), any(), any(), anyInt(), anyInt(), any(), anyInt(), any(), any());
+        }).when(mMockScreenshotHelper).takeScreenshot(any(), any(), any());
         mGameServiceProviderInstance.start();
         startTask(taskId, GAME_A_MAIN_ACTIVITY);
         mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index c15f6a9..5792ecb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -17,7 +17,6 @@
 package com.android.server.accessibility;
 
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
 
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.is;
@@ -301,9 +300,7 @@
         mSystemActionPerformer.performSystemAction(
                 AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
         verify(mMockScreenshotHelper).takeScreenshot(
-                eq(TAKE_SCREENSHOT_FULLSCREEN),
-                eq(SCREENSHOT_ACCESSIBILITY_ACTIONS),
-                any(Handler.class), any());
+                eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
     }
 
     // PendingIntent is a final class and cannot be mocked. So we are using this
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 2edb909..52fade1 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -424,6 +424,37 @@
     }
 
     /**
+     * Tests that HighBrightnessModeMetadata is non-null on all display devices.
+     */
+    @Test
+    public void testHighBrightnessModeMetadataNonNull() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        // Add the FakeDisplayDevice
+        FakeDisplayDevice displayDevice = new FakeDisplayDevice("unique_hbm_device");
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+
+        displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
+
+        LogicalDisplay logicalDisplay = new LogicalDisplay(1, 1, displayDevice);
+        HighBrightnessModeMetadata hbmMeta =
+                displayManager.getHighBrightnessModeMetadata(logicalDisplay);
+
+        assertNotNull(hbmMeta);
+
+        // Check is Hbm metadata is correctly added for the display device.
+        String uniqueId = displayDevice.getUniqueId();
+        assertTrue(uniqueId.equals("unique_hbm_device"));
+        assertTrue(displayManager.mHighBrightnessModeMetadataMap.containsKey(uniqueId));
+        HighBrightnessModeMetadata hbmMetaFromMap =
+                displayManager.mHighBrightnessModeMetadataMap.get(uniqueId);
+        assertEquals(hbmMeta, hbmMetaFromMap);
+    }
+
+    /**
      * Tests that we get a Runtime exception when we cannot initialize the default display.
      */
     @Test
@@ -1349,6 +1380,11 @@
             super(null, null, "", mContext);
         }
 
+        FakeDisplayDevice(String uniqueDeviceId) {
+            super(null, null, uniqueDeviceId, mContext);
+        }
+
+
         public void setDisplayDeviceInfo(DisplayDeviceInfo displayDeviceInfo) {
             mDisplayDeviceInfo = displayDeviceInfo;
         }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index af39dd4..2cc6bd5 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -2011,6 +2011,74 @@
                 eq(lightSensorTwo), anyInt(), any(Handler.class));
     }
 
+    @Test
+    public void testAuthenticationPossibleSetsPhysicalRateRangesToMax() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        // don't call director.start(createMockSensorManager());
+        // DisplayObserver will reset mSupportedModesByDisplay
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsHbmListener> captor =
+                ArgumentCaptor.forClass(IUdfpsHbmListener.class);
+        verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
+
+        captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+        assertThat(vote.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(vote.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+    }
+
+    @Test
+    public void testAuthenticationPossibleUnsetsVote() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsHbmListener> captor =
+                ArgumentCaptor.forClass(IUdfpsHbmListener.class);
+        verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
+        captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
+        captor.getValue().onAuthenticationPossible(DISPLAY_ID, false);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+        assertNull(vote);
+    }
+
+    @Test
+    public void testUdfpsRequestSetsPhysicalRateRangesToMax() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        // don't call director.start(createMockSensorManager());
+        // DisplayObserver will reset mSupportedModesByDisplay
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsHbmListener> captor =
+                ArgumentCaptor.forClass(IUdfpsHbmListener.class);
+        verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
+
+        captor.getValue().onHbmEnabled(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
+        assertThat(vote.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+        assertThat(vote.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+    }
+
+    @Test
+    public void testUdfpsRequestUnsetsUnsetsVote() throws RemoteException {
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+        director.start(createMockSensorManager());
+        director.onBootCompleted();
+        ArgumentCaptor<IUdfpsHbmListener> captor =
+                ArgumentCaptor.forClass(IUdfpsHbmListener.class);
+        verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
+        captor.getValue().onHbmEnabled(DISPLAY_ID);
+        captor.getValue().onHbmEnabled(DISPLAY_ID);
+
+        Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
+        assertNull(vote);
+    }
+
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
         return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 6790ad9..a7da2417 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.DEFAULT_DISPLAY_GROUP;
 import static android.view.Display.TYPE_EXTERNAL;
@@ -441,6 +442,11 @@
                 /* isOverrideActive= */false,
                 /* isInteractive= */true,
                 /* isBootCompleted= */true));
+        assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+                INVALID_DEVICE_STATE,
+                /* isOverrideActive= */false,
+                /* isInteractive= */true,
+                /* isBootCompleted= */true));
     }
 
     @Test
@@ -591,6 +597,7 @@
         // 2) Mark the displays as STATE_OFF so that it can continue with transition
         // 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
         // 4) Dispatch handler events.
+        mLogicalDisplayMapper.onBootCompleted();
         mLogicalDisplayMapper.setDeviceStateLocked(0, false);
         mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
         mLooper.moveTimeForward(1000);
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
new file mode 100644
index 0000000..6c73f71
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamOverlayServiceTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.server.dreams;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.dreams.DreamOverlayService;
+import android.service.dreams.IDreamOverlay;
+import android.service.dreams.IDreamOverlayCallback;
+import android.service.dreams.IDreamOverlayClient;
+import android.service.dreams.IDreamOverlayClientCallback;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * A collection of tests to exercise {@link DreamOverlayService}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamOverlayServiceTest {
+    private static final ComponentName FIRST_DREAM_COMPONENT =
+            ComponentName.unflattenFromString("com.foo.bar/.DreamService");
+    private static final ComponentName SECOND_DREAM_COMPONENT =
+            ComponentName.unflattenFromString("com.foo.baz/.DreamService");
+
+    @Mock
+    WindowManager.LayoutParams mLayoutParams;
+
+    @Mock
+    IDreamOverlayCallback mOverlayCallback;
+
+    /**
+     * {@link TestDreamOverlayService} is a simple {@link DreamOverlayService} implementation for
+     * tracking interactions across {@link IDreamOverlay} binder interface. The service reports
+     * interactions to a {@link Monitor} instance provided at construction.
+     */
+    private static class TestDreamOverlayService extends DreamOverlayService {
+        /**
+         * An interface implemented to be informed when the corresponding methods in
+         * {@link TestDreamOverlayService} are invoked.
+         */
+        interface Monitor {
+            void onStartDream();
+            void onEndDream();
+            void onWakeUp();
+        }
+
+        private final Monitor mMonitor;
+
+        TestDreamOverlayService(Monitor monitor) {
+            super();
+            mMonitor = monitor;
+        }
+
+        @Override
+        public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+            mMonitor.onStartDream();
+        }
+
+        @Override
+        public void onEndDream() {
+            mMonitor.onEndDream();
+            super.onEndDream();
+        }
+
+        @Override
+        public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+            mMonitor.onWakeUp();
+            super.onWakeUp(onCompleteCallback);
+        }
+    }
+
+    /**
+     * A {@link IDreamOverlayClientCallback} implementation that captures the requested client.
+     */
+    private static class OverlayClientCallback extends IDreamOverlayClientCallback.Stub {
+        public IDreamOverlayClient retrievedClient;
+        @Override
+        public void onDreamOverlayClient(IDreamOverlayClient client) throws RemoteException {
+            retrievedClient = client;
+        }
+    }
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /**
+     * Verifies that only the currently started dream is able to affect the overlay.
+     */
+    @Test
+    public void testOverlayClientInteraction() throws RemoteException {
+        final TestDreamOverlayService.Monitor monitor = Mockito.mock(
+                TestDreamOverlayService.Monitor.class);
+        final TestDreamOverlayService service = new TestDreamOverlayService(monitor);
+        final IBinder binder = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(binder);
+
+        // Create two overlay clients and ensure they are unique.
+        final IDreamOverlayClient firstClient = getClient(overlay);
+        assertThat(firstClient).isNotNull();
+
+        final IDreamOverlayClient secondClient = getClient(overlay);
+        assertThat(secondClient).isNotNull();
+
+        assertThat(firstClient).isNotEqualTo(secondClient);
+
+        // Start a dream with the first client and ensure the dream is now active from the
+        // overlay's perspective.
+        firstClient.startDream(mLayoutParams, mOverlayCallback,
+                FIRST_DREAM_COMPONENT.flattenToString(), false);
+
+
+        verify(monitor).onStartDream();
+        assertThat(service.getDreamComponent()).isEqualTo(FIRST_DREAM_COMPONENT);
+
+        Mockito.clearInvocations(monitor);
+
+        // Start a dream from the second client and verify that the overlay has both cycled to
+        // the new dream (ended/started).
+        secondClient.startDream(mLayoutParams, mOverlayCallback,
+                SECOND_DREAM_COMPONENT.flattenToString(), false);
+
+        verify(monitor).onEndDream();
+        verify(monitor).onStartDream();
+        assertThat(service.getDreamComponent()).isEqualTo(SECOND_DREAM_COMPONENT);
+
+        Mockito.clearInvocations(monitor);
+
+        // Verify that interactions with the first, now inactive client don't affect the overlay.
+        firstClient.endDream();
+        verify(monitor, never()).onEndDream();
+
+        firstClient.wakeUp();
+        verify(monitor, never()).onWakeUp();
+    }
+
+    private static IDreamOverlayClient getClient(IDreamOverlay overlay) throws RemoteException {
+        final OverlayClientCallback callback = new OverlayClientCallback();
+        overlay.getClient(callback);
+        return callback.retrievedClient;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index b12e6ad..a27602d 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -500,6 +500,7 @@
         // The cached conversations are above the limit because every conversation has active
         // notifications. To uncache one of them, the notifications for that conversation need to
         // be dismissed.
+        String notificationKey = "";
         for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
             String shortcutId = TEST_SHORTCUT_ID + i;
             ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
@@ -507,11 +508,13 @@
             shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
             mDataManager.addOrUpdateConversationInfo(shortcut);
             when(mNotification.getShortcutId()).thenReturn(shortcutId);
-            sendGenericNotification();
+            notificationKey = String.format("notification-key-%d", i);
+            sendGenericNotificationWithKey(notificationKey);
         }
 
         // Post another notification for the last conversation.
-        sendGenericNotification();
+        String otherNotificationKey = "other-notification-key";
+        sendGenericNotificationWithKey(otherNotificationKey);
 
         // Removing one of the two notifications does not un-cache the shortcut.
         listenerService.onNotificationRemoved(mGenericSbn, null,
@@ -520,6 +523,7 @@
                 anyInt(), any(), anyString(), any(), anyInt(), anyInt());
 
         // Removing the second notification un-caches the shortcut.
+        when(mGenericSbn.getKey()).thenReturn(notificationKey);
         listenerService.onNotificationRemoved(mGenericSbn, null,
                 NotificationListenerService.REASON_CANCEL_ALL);
         verify(mShortcutServiceInternal).uncacheShortcuts(
@@ -687,6 +691,63 @@
     }
 
     @Test
+    public void testGetConversation_trackActiveConversations() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNull();
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNotNull();
+
+        sendGenericNotification();
+        sendGenericNotification();
+        ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        assertTrue(result.hasActiveNotifications());
+
+        // Both generic notifications have the same notification key, so a single dismiss will
+        // remove both of them.
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationRemoved(mGenericSbn, null,
+                NotificationListenerService.REASON_CANCEL);
+        ConversationChannel resultTwo = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        assertFalse(resultTwo.hasActiveNotifications());
+    }
+
+    @Test
+    public void testGetConversation_unsyncedShortcut() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNotNull();
+        assertThat(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
+                .getConversationStore()
+                .getConversation(TEST_SHORTCUT_ID)).isNotNull();
+
+        when(mShortcutServiceInternal.getShortcuts(
+                anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
+                anyInt(), anyInt(), anyInt(), anyInt()))
+                .thenReturn(Collections.emptyList());
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNull();
+
+        // Conversation is removed from store as there is no matching shortcut in ShortcutManager
+        assertThat(mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY)
+                .getConversationStore()
+                .getConversation(TEST_SHORTCUT_ID)).isNull();
+        verify(mNotificationManagerInternal)
+                .onConversationRemoved(TEST_PKG_NAME, TEST_PKG_UID, Set.of(TEST_SHORTCUT_ID));
+    }
+
+    @Test
     public void testOnNotificationChannelModified() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
         assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
@@ -1294,7 +1355,7 @@
 
         sendGenericNotification();
 
-       mDataManager.getRecentConversations(USER_ID_PRIMARY);
+        mDataManager.getRecentConversations(USER_ID_PRIMARY);
 
         verify(mShortcutServiceInternal).getShortcuts(
                 anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
@@ -1665,6 +1726,12 @@
     // "Sends" a notification to a non-customized notification channel - the notification channel
     // is something generic like "messages" and the notification has a  shortcut id
     private void sendGenericNotification() {
+        sendGenericNotificationWithKey(GENERIC_KEY);
+    }
+
+    // "Sends" a notification to a non-customized notification channel with the specified key.
+    private void sendGenericNotificationWithKey(String key) {
+        when(mGenericSbn.getKey()).thenReturn(key);
         when(mNotification.getChannelId()).thenReturn(PARENT_NOTIFICATION_CHANNEL_ID);
         doAnswer(invocationOnMock -> {
             NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
@@ -1680,7 +1747,7 @@
                     mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null,
                     false, false, false, null, 0, false, 0);
             return true;
-        }).when(mRankingMap).getRanking(eq(GENERIC_KEY),
+        }).when(mRankingMap).getRanking(eq(key),
                 any(NotificationListenerService.Ranking.class));
         NotificationListenerService listenerService =
                 mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
index 3de65c1..1b3a199 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
@@ -132,6 +132,19 @@
         assertThat(mFinalizedRotation).isEqualTo(DEFAULT_SENSOR_ROTATION);
     }
 
+    @Test
+    public void testOnSensorChanged_screenLocked_doNotCallRotationResolverReturnDefaultRotation() {
+        mWindowOrientationListener = new TestableWindowOrientationListener(mMockContext,
+                mHandler, /* defaultRotation */ Surface.ROTATION_180);
+        mWindowOrientationListener.mRotationResolverService = mFakeRotationResolverInternal;
+        mWindowOrientationListener.mIsScreenLocked = true;
+
+        mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
+
+        assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse();
+        assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_180);
+    }
+
     static final class TestableRotationResolver extends RotationResolverInternal {
         @Surface.Rotation
         RotationResolverCallbackInternal mCallback;
@@ -166,21 +179,17 @@
         }
     }
 
-    @Test
-    public void testOnSensorChanged_inLockScreen_doNotCallRotationResolver() {
-        mWindowOrientationListener.mIsScreenLocked = true;
-
-        mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent);
-
-        assertThat(mWindowOrientationListener.mIsOnProposedRotationChangedCalled).isFalse();
-    }
-
     final class TestableWindowOrientationListener extends WindowOrientationListener {
         private boolean mIsOnProposedRotationChangedCalled = false;
         private boolean mIsScreenLocked;
 
         TestableWindowOrientationListener(Context context, Handler handler) {
-            super(context, handler);
+            this(context, handler, Surface.ROTATION_0);
+        }
+
+        TestableWindowOrientationListener(Context context, Handler handler,
+                @Surface.Rotation int defaultRotation) {
+            super(context, handler, defaultRotation);
             this.mOrientationJudge = new OrientationSensorJudge();
         }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 7986043..8b1384e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1582,6 +1582,55 @@
     }
 
     @Test
+    public void testSetComponentState_differentUsers() throws Exception {
+        Context context = mock(Context.class);
+        PackageManager pm = mock(PackageManager.class);
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+        when(context.getPackageName()).thenReturn(mContext.getPackageName());
+        when(context.getUserId()).thenReturn(mContext.getUserId());
+        when(context.getPackageManager()).thenReturn(pm);
+        when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_COMPONENT);
+        ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+        addExpectedServices(service, Arrays.asList("a"), mZero.id);
+        addExpectedServices(service, Arrays.asList("a"), mTen.id);
+        when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            ServiceConnection sc = (ServiceConnection) args[1];
+            sc.onServiceConnected(cn, mock(IBinder.class));
+            return true;
+        });
+        service.addApprovedList("a/a", 0, true);
+        service.addApprovedList("a/a", 10, false);
+
+        service.registerService(cn, mZero.id);
+        assertTrue(service.isBound(cn, mZero.id));
+
+        service.onUserSwitched(mTen.id);
+        assertFalse(service.isBound(cn, mZero.id));
+        service.registerService(cn, mTen.id);
+        assertTrue(service.isBound(cn, mTen.id));
+
+        service.setComponentState(cn, mTen.id, false);
+        assertFalse(service.isBound(cn, mZero.id));
+        assertFalse(service.isBound(cn, mTen.id));
+
+        // Service should be rebound on user 0, since it was only disabled for user 10.
+        service.onUserSwitched(mZero.id);
+        assertTrue(service.isBound(cn, mZero.id));
+        assertFalse(service.isBound(cn, mTen.id));
+
+        // Service should stay unbound on going back to user 10.
+        service.onUserSwitched(mTen.id);
+        assertFalse(service.isBound(cn, mZero.id));
+        assertFalse(service.isBound(cn, mTen.id));
+    }
+    @Test
     public void testOnPackagesChanged_nullValuesPassed_noNullPointers() {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1847,7 +1896,7 @@
     }
 
     private void addExpectedServices(final ManagedServices service, final List<String> packages,
-            int userId) {
+            int userId) throws Exception {
         ManagedServices.Config config = service.getConfig();
         when(mPm.queryIntentServicesAsUser(any(), anyInt(), eq(userId))).
                 thenAnswer(new Answer<List<ResolveInfo>>() {
@@ -1876,6 +1925,20 @@
                         return new ArrayList<>();
                     }
                 });
+
+        when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+                (Answer<ServiceInfo>) invocation -> {
+                    ComponentName invocationCn = invocation.getArgument(0);
+                    if (invocationCn != null && packages.contains(invocationCn.getPackageName())) {
+                        ServiceInfo serviceInfo = new ServiceInfo();
+                        serviceInfo.packageName = invocationCn.getPackageName();
+                        serviceInfo.name = invocationCn.getClassName();
+                        serviceInfo.permission = service.getConfig().bindPermission;
+                        return serviceInfo;
+                    }
+                    return null;
+                }
+        );
     }
 
     private List<String> stringToList(String list) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3f3b052..96e2a09 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1645,6 +1645,46 @@
     }
 
     @Test
+    public void testEnqueueNotificationWithTag_nullAction_fixed() throws Exception {
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .addAction(new Notification.Action.Builder(null, "one", null).build())
+                .addAction(new Notification.Action.Builder(null, "two", null).build())
+                .addAction(new Notification.Action.Builder(null, "three", null).build())
+                .build();
+        n.actions[1] = null;
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+        waitForIdle();
+
+        StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
+        assertThat(posted).hasLength(1);
+        assertThat(posted[0].getNotification().actions).hasLength(2);
+        assertThat(posted[0].getNotification().actions[0].title.toString()).isEqualTo("one");
+        assertThat(posted[0].getNotification().actions[1].title.toString()).isEqualTo("three");
+    }
+
+    @Test
+    public void testEnqueueNotificationWithTag_allNullActions_fixed() throws Exception {
+        Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .addAction(new Notification.Action.Builder(null, "one", null).build())
+                .addAction(new Notification.Action.Builder(null, "two", null).build())
+                .build();
+        n.actions[0] = null;
+        n.actions[1] = null;
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0, n, 0);
+        waitForIdle();
+
+        StatusBarNotification[] posted = mBinderService.getActiveNotifications(PKG);
+        assertThat(posted).hasLength(1);
+        assertThat(posted[0].getNotification().actions).isNull();
+    }
+
+    @Test
     public void testCancelNonexistentNotification() throws Exception {
         mBinderService.cancelNotificationWithTag(PKG, PKG,
                 "testCancelNonexistentNotification", 0, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index eed32d7..bb20244 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -249,6 +249,19 @@
     }
 
     /**
+     * Ensures it updates recent tasks order when the last resumed activity changed.
+     */
+    @Test
+    public void testUpdateRecentTasksForTopResumed() {
+        spyOn(mSupervisor.mRecentTasks);
+        final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Task task = activity.getTask();
+
+        mAtm.setLastResumedActivityUncheckLocked(activity, "test");
+        verify(mSupervisor.mRecentTasks).add(eq(task));
+    }
+
+    /**
      * Ensures that a trusted display can launch arbitrary activity and an untrusted display can't.
      */
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
index 86732c9..2a28ae2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DeviceStateControllerTests.java
@@ -16,13 +16,12 @@
 
 package com.android.server.wm;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -32,9 +31,10 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
+
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 
 import java.util.function.Consumer;
 
@@ -48,92 +48,76 @@
 @Presubmit
 public class DeviceStateControllerTests {
 
-    private DeviceStateController.FoldStateListener mFoldStateListener;
     private DeviceStateController mTarget;
     private DeviceStateControllerBuilder mBuilder;
 
     private Context mMockContext;
-    private Handler mMockHandler;
-    private Resources mMockRes;
     private DeviceStateManager mMockDeviceStateManager;
-
-    private Consumer<DeviceStateController.FoldState> mDelegate;
-    private DeviceStateController.FoldState mCurrentState = DeviceStateController.FoldState.UNKNOWN;
+    private DeviceStateController.DeviceState mCurrentState =
+            DeviceStateController.DeviceState.UNKNOWN;
 
     @Before
     public void setUp() {
         mBuilder = new DeviceStateControllerBuilder();
-        mCurrentState = DeviceStateController.FoldState.UNKNOWN;
+        mCurrentState = DeviceStateController.DeviceState.UNKNOWN;
     }
 
-    private void initialize(boolean supportFold, boolean supportHalfFold) throws Exception {
+    private void initialize(boolean supportFold, boolean supportHalfFold) {
         mBuilder.setSupportFold(supportFold, supportHalfFold);
-        mDelegate = (newFoldState) -> {
+        Consumer<DeviceStateController.DeviceState> delegate = (newFoldState) -> {
             mCurrentState = newFoldState;
         };
-        mBuilder.setDelegate(mDelegate);
+        mBuilder.setDelegate(delegate);
         mBuilder.build();
-        verifyFoldStateListenerRegistration(1);
+        verify(mMockDeviceStateManager).registerCallback(any(), any());
     }
 
     @Test
-    public void testInitialization() throws Exception {
+    public void testInitialization() {
         initialize(true /* supportFold */, true /* supportHalfFolded */);
-        mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
-        assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
+        mTarget.onStateChanged(mOpenDeviceStates[0]);
+        assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
     }
 
     @Test
-    public void testInitializationWithNoFoldSupport() throws Exception {
+    public void testInitializationWithNoFoldSupport() {
         initialize(false /* supportFold */, false /* supportHalfFolded */);
-        mFoldStateListener.onStateChanged(mFoldedStates[0]);
+        mTarget.onStateChanged(mFoldedStates[0]);
         // Note that the folded state is ignored.
-        assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
+        assertEquals(DeviceStateController.DeviceState.UNKNOWN, mCurrentState);
     }
 
     @Test
-    public void testWithFoldSupported() throws Exception {
+    public void testWithFoldSupported() {
         initialize(true /* supportFold */, false /* supportHalfFolded */);
-        mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
-        assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
-        mFoldStateListener.onStateChanged(mFoldedStates[0]);
-        assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED);
-        mFoldStateListener.onStateChanged(mHalfFoldedStates[0]);
-        assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN); // Ignored
+        mTarget.onStateChanged(mOpenDeviceStates[0]);
+        assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
+        mTarget.onStateChanged(mFoldedStates[0]);
+        assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
+        mTarget.onStateChanged(mHalfFoldedStates[0]);
+        assertEquals(DeviceStateController.DeviceState.UNKNOWN, mCurrentState); // Ignored
     }
 
     @Test
-    public void testWithHalfFoldSupported() throws Exception {
+    public void testWithHalfFoldSupported() {
         initialize(true /* supportFold */, true /* supportHalfFolded */);
-        mFoldStateListener.onStateChanged(mUnfoldedStates[0]);
-        assertEquals(mCurrentState, DeviceStateController.FoldState.OPEN);
-        mFoldStateListener.onStateChanged(mFoldedStates[0]);
-        assertEquals(mCurrentState, DeviceStateController.FoldState.FOLDED);
-        mFoldStateListener.onStateChanged(mHalfFoldedStates[0]);
-        assertEquals(mCurrentState, DeviceStateController.FoldState.HALF_FOLDED);
+        mTarget.onStateChanged(mOpenDeviceStates[0]);
+        assertEquals(DeviceStateController.DeviceState.OPEN, mCurrentState);
+        mTarget.onStateChanged(mFoldedStates[0]);
+        assertEquals(DeviceStateController.DeviceState.FOLDED, mCurrentState);
+        mTarget.onStateChanged(mHalfFoldedStates[0]);
+        assertEquals(DeviceStateController.DeviceState.HALF_FOLDED, mCurrentState);
     }
 
-
     private final int[] mFoldedStates = {0};
-    private final int[] mUnfoldedStates = {1};
+    private final int[] mOpenDeviceStates = {1};
     private final int[] mHalfFoldedStates = {2};
-
-
-    private void verifyFoldStateListenerRegistration(int numOfInvocation) {
-        final ArgumentCaptor<DeviceStateController.FoldStateListener> listenerCaptor =
-                ArgumentCaptor.forClass(DeviceStateController.FoldStateListener.class);
-        verify(mMockDeviceStateManager, times(numOfInvocation)).registerCallback(
-                any(),
-                listenerCaptor.capture());
-        if (numOfInvocation > 0) {
-            mFoldStateListener = listenerCaptor.getValue();
-        }
-    }
+    private final int[] mRearDisplayStates = {3};
 
     private class DeviceStateControllerBuilder {
         private boolean mSupportFold = false;
         private boolean mSupportHalfFold = false;
-        private Consumer<DeviceStateController.FoldState> mDelegate;
+        private Consumer<DeviceStateController.DeviceState> mDelegate;
 
         DeviceStateControllerBuilder setSupportFold(
                 boolean supportFold, boolean supportHalfFold) {
@@ -143,34 +127,44 @@
         }
 
         DeviceStateControllerBuilder setDelegate(
-                Consumer<DeviceStateController.FoldState> delegate) {
+                Consumer<DeviceStateController.DeviceState> delegate) {
             mDelegate = delegate;
             return this;
         }
 
         private void mockFold(boolean enableFold, boolean enableHalfFold) {
+            if (enableFold || enableHalfFold) {
+                when(mMockContext.getResources()
+                        .getIntArray(R.array.config_openDeviceStates))
+                        .thenReturn(mOpenDeviceStates);
+                when(mMockContext.getResources()
+                        .getIntArray(R.array.config_rearDisplayDeviceStates))
+                        .thenReturn(mRearDisplayStates);
+            }
+
             if (enableFold) {
-                when(mMockContext.getResources().getIntArray(
-                        com.android.internal.R.array.config_foldedDeviceStates))
+                when(mMockContext.getResources()
+                        .getIntArray(R.array.config_foldedDeviceStates))
                         .thenReturn(mFoldedStates);
             }
             if (enableHalfFold) {
-                when(mMockContext.getResources().getIntArray(
-                        com.android.internal.R.array.config_halfFoldedDeviceStates))
+                when(mMockContext.getResources()
+                        .getIntArray(R.array.config_halfFoldedDeviceStates))
                         .thenReturn(mHalfFoldedStates);
             }
         }
 
-        private void build() throws Exception {
+        private void build() {
             mMockContext = mock(Context.class);
-            mMockRes = mock(Resources.class);
-            when(mMockContext.getResources()).thenReturn((mMockRes));
             mMockDeviceStateManager = mock(DeviceStateManager.class);
             when(mMockContext.getSystemService(DeviceStateManager.class))
                     .thenReturn(mMockDeviceStateManager);
+            Resources mockRes = mock(Resources.class);
+            when(mMockContext.getResources()).thenReturn((mockRes));
             mockFold(mSupportFold, mSupportHalfFold);
-            mMockHandler = mock(Handler.class);
-            mTarget = new DeviceStateController(mMockContext, mMockHandler, mDelegate);
+            Handler mockHandler = mock(Handler.class);
+            mTarget = new DeviceStateController(mMockContext, mockHandler);
+            mTarget.registerDeviceStateCallback(mDelegate);
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 98e68ca..dc12469 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1736,7 +1736,7 @@
 
         // No need to apply rotation if the display ignores orientation request.
         doCallRealMethod().when(displayContent).rotationForActivityInDifferentOrientation(any());
-        pinnedActivity.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+        pinnedActivity.setOverrideOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         displayContent.setIgnoreOrientationRequest(true);
         assertEquals(WindowConfiguration.ROTATION_UNDEFINED,
                 displayContent.rotationForActivityInDifferentOrientation(pinnedActivity));
@@ -2064,7 +2064,8 @@
         // Update the forced size and density in settings and the unique id to simualate a display
         // remap.
         dc.mWmService.mDisplayWindowSettings.setForcedSize(dc, forcedWidth, forcedHeight);
-        dc.mWmService.mDisplayWindowSettings.setForcedDensity(dc, forcedDensity, 0 /* userId */);
+        dc.mWmService.mDisplayWindowSettings.setForcedDensity(displayInfo, forcedDensity,
+                0 /* userId */);
         dc.mCurrentUniqueDisplayId = mDisplayInfo.uniqueId + "-test";
         // Trigger display changed.
         dc.onDisplayChanged();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 45b30b2..4954e89 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
@@ -30,7 +31,9 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertEquals;
@@ -58,6 +61,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -128,6 +133,69 @@
     }
 
     @Test
+    public void testOpenedCameraInSplitScreen_showToast() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        spyOn(mTask);
+        spyOn(mDisplayRotationCompatPolicy);
+        doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+        doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        verify(mDisplayRotationCompatPolicy).showToast(
+                R.string.display_rotation_camera_compat_toast_in_split_screen);
+    }
+
+    @Test
+    public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() {
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                    /* checkDeviceConfig */ anyBoolean()))
+                .thenReturn(false);
+        spyOn(mDisplayRotationCompatPolicy);
+
+        mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+        verify(mDisplayRotationCompatPolicy, never()).showToast(
+                R.string.display_rotation_camera_compat_toast_after_rotation);
+    }
+
+    @Test
+    public void testOnScreenRotationAnimationFinished_noOpenCamera_doNotShowToast() {
+        spyOn(mDisplayRotationCompatPolicy);
+
+        mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+        verify(mDisplayRotationCompatPolicy, never()).showToast(
+                R.string.display_rotation_camera_compat_toast_after_rotation);
+    }
+
+    @Test
+    public void testOnScreenRotationAnimationFinished_notFullscreen_doNotShowToast() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+        spyOn(mDisplayRotationCompatPolicy);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+        verify(mDisplayRotationCompatPolicy, never()).showToast(
+                R.string.display_rotation_camera_compat_toast_after_rotation);
+    }
+
+    @Test
+    public void testOnScreenRotationAnimationFinished_showToast() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        spyOn(mDisplayRotationCompatPolicy);
+
+        mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+        verify(mDisplayRotationCompatPolicy).showToast(
+                R.string.display_rotation_camera_compat_toast_after_rotation);
+    }
+
+    @Test
     public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
         when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
                     /* checkDeviceConfig */ anyBoolean()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 4ce43e1..ed2b0a3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -56,6 +56,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
 import android.os.PowerManagerInternal;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
@@ -111,6 +112,7 @@
     private ContentResolver mMockResolver;
     private FakeSettingsProvider mFakeSettingsProvider;
     private StatusBarManagerInternal mMockStatusBarManagerInternal;
+    private DeviceStateManager mMockDeviceStateManager;
 
     // Fields below are callbacks captured from test target.
     private ContentObserver mShowRotationSuggestionsObserver;
@@ -120,6 +122,7 @@
 
     private DisplayRotationBuilder mBuilder;
 
+    private DeviceStateController mDeviceStateController;
     private DisplayRotation mTarget;
 
     @BeforeClass
@@ -484,6 +487,34 @@
                 SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
     }
 
+    @Test
+    public void testReverseRotation() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+
+        thawRotation();
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_270));
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0));
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_180));
+    }
+
     private boolean waitForUiHandler() {
         final CountDownLatch latch = new CountDownLatch(1);
         UiThread.getHandler().post(latch::countDown);
@@ -705,7 +736,7 @@
 
         enableOrientationSensor();
 
-        mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN);
+        mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN);
         freezeRotation(Surface.ROTATION_270);
 
         mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_0));
@@ -715,7 +746,7 @@
                 SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
 
         // ... until half-fold
-        mTarget.foldStateChanged(DeviceStateController.FoldState.HALF_FOLDED);
+        mTarget.foldStateChanged(DeviceStateController.DeviceState.HALF_FOLDED);
         assertTrue(waitForUiHandler());
         verify(sMockWm).updateRotation(false, false);
         assertTrue(waitForUiHandler());
@@ -723,7 +754,7 @@
                 SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
 
         // ... then transition back to flat
-        mTarget.foldStateChanged(DeviceStateController.FoldState.OPEN);
+        mTarget.foldStateChanged(DeviceStateController.DeviceState.OPEN);
         assertTrue(waitForUiHandler());
         verify(sMockWm, atLeast(1)).updateRotation(false, false);
         assertTrue(waitForUiHandler());
@@ -1097,8 +1128,14 @@
 
             mMockDisplayWindowSettings = mock(DisplayWindowSettings.class);
 
+            mMockDeviceStateManager = mock(DeviceStateManager.class);
+            when(mMockContext.getSystemService(eq(DeviceStateManager.class)))
+                    .thenReturn(mMockDeviceStateManager);
+
+            mDeviceStateController = mock(DeviceStateController.class);
             mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress,
-                    mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object()) {
+                    mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object(),
+                    mDeviceStateController) {
                 @Override
                 DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
                         WindowManagerService service, DisplayContent displayContent) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index c398a0a..fb4f2ee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -236,8 +236,8 @@
 
     @Test
     public void testSetForcedDensity() {
-        mDisplayWindowSettings.setForcedDensity(mSecondaryDisplay, 600 /* density */,
-                0 /* userId */);
+        mDisplayWindowSettings.setForcedDensity(mSecondaryDisplay.getDisplayInfo(),
+                600 /* density */, 0 /* userId */);
         mDisplayWindowSettings.applySettingsToDisplayLocked(mSecondaryDisplay);
 
         assertEquals(600 /* density */, mSecondaryDisplay.mBaseDisplayDensity);
@@ -439,8 +439,9 @@
     public void testDisplayWindowSettingsAppliedOnDisplayReady() {
         // Set forced densities for two displays in DisplayWindowSettings
         final DisplayContent dc = createMockSimulatedDisplay();
-        mDisplayWindowSettings.setForcedDensity(mPrimaryDisplay, 123, 0 /* userId */);
-        mDisplayWindowSettings.setForcedDensity(dc, 456, 0 /* userId */);
+        mDisplayWindowSettings.setForcedDensity(mPrimaryDisplay.getDisplayInfo(), 123,
+                0 /* userId */);
+        mDisplayWindowSettings.setForcedDensity(dc.getDisplayInfo(), 456, 0 /* userId */);
 
         // Apply settings to displays - the settings will be stored, but config will not be
         // recalculated immediately.
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index ead1a86..12b7c9d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -51,7 +51,7 @@
  * Tests for the {@link LetterboxConfiguration} class.
  *
  * Build/Install/Run:
- *  atest WmTests:LetterboxConfigurationTests
+ *  atest WmTests:LetterboxConfigurationTest
  */
 @SmallTest
 @Presubmit
@@ -243,18 +243,18 @@
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
         mLetterboxConfiguration.setIsCompatFakeFocusEnabled(false);
-        assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());
+        assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
 
         // Set runtime flag to false and build time flag to true
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "false", false);
         mLetterboxConfiguration.setIsCompatFakeFocusEnabled(true);
-        assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());
+        assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
 
         // Set runtime flag to true so that both are enabled
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
-        assertTrue(mLetterboxConfiguration.isCompatFakeFocusEnabledOnDevice());
+        assertTrue(mLetterboxConfiguration.isCompatFakeFocusEnabled());
 
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, Boolean.toString(wasFakeFocusEnabled),
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5e087f0..0d20f17 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -16,33 +16,65 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
+import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
+import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyString;
 
+import android.annotation.Nullable;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.Property;
+import android.content.res.Resources;
+import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.RoundedCorner;
+import android.view.RoundedCorners;
+import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
 import org.junit.Before;
@@ -61,14 +93,24 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class LetterboxUiControllerTest extends WindowTestsBase {
+    private static final int TASKBAR_COLLAPSED_HEIGHT = 10;
+    private static final int TASKBAR_EXPANDED_HEIGHT = 20;
+    private static final int SCREEN_WIDTH = 200;
+    private static final int SCREEN_HEIGHT = 100;
+    private static final Rect TASKBAR_COLLAPSED_BOUNDS = new Rect(0,
+            SCREEN_HEIGHT - TASKBAR_COLLAPSED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
+    private static final Rect TASKBAR_EXPANDED_BOUNDS = new Rect(0,
+            SCREEN_HEIGHT - TASKBAR_EXPANDED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
 
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
     private ActivityRecord mActivity;
+    private Task mTask;
     private DisplayContent mDisplayContent;
     private LetterboxUiController mController;
     private LetterboxConfiguration mLetterboxConfiguration;
+    private final Rect mLetterboxedPortraitTaskBounds = new Rect();
 
     @Before
     public void setUp() throws Exception {
@@ -308,6 +350,388 @@
         assertTrue(mController.shouldForceRotateForCameraCompat());
     }
 
+    @Test
+    public void testGetCropBoundsIfNeeded_noCrop() {
+        final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+
+        // Do not apply crop if taskbar is collapsed
+        taskbar.setFrame(TASKBAR_COLLAPSED_BOUNDS);
+        assertNull(mController.getExpandedTaskbarOrNull(mainWindow));
+
+        mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 4,
+                SCREEN_WIDTH - SCREEN_WIDTH / 4, SCREEN_HEIGHT - SCREEN_HEIGHT / 4);
+
+        final Rect noCrop = mController.getCropBoundsIfNeeded(mainWindow);
+        assertNotEquals(null, noCrop);
+        assertEquals(0, noCrop.left);
+        assertEquals(0, noCrop.top);
+        assertEquals(mLetterboxedPortraitTaskBounds.width(), noCrop.right);
+        assertEquals(mLetterboxedPortraitTaskBounds.height(), noCrop.bottom);
+    }
+
+    @Test
+    public void testGetCropBoundsIfNeeded_appliesCrop() {
+        final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+
+        // Apply crop if taskbar is expanded
+        taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
+        assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow));
+
+        mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4,
+                SCREEN_HEIGHT);
+
+        final Rect crop = mController.getCropBoundsIfNeeded(mainWindow);
+        assertNotEquals(null, crop);
+        assertEquals(0, crop.left);
+        assertEquals(0, crop.top);
+        assertEquals(mLetterboxedPortraitTaskBounds.width(), crop.right);
+        assertEquals(mLetterboxedPortraitTaskBounds.height() - TASKBAR_EXPANDED_HEIGHT,
+                crop.bottom);
+    }
+
+    @Test
+    public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
+        final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+        final float scaling = 2.0f;
+
+        // Apply crop if taskbar is expanded
+        taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
+        assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow));
+        // With SizeCompat scaling
+        doReturn(true).when(mActivity).inSizeCompatMode();
+        mainWindow.mInvGlobalScale = scaling;
+
+        mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4,
+                SCREEN_HEIGHT);
+
+        final int appWidth = mLetterboxedPortraitTaskBounds.width();
+        final int appHeight = mLetterboxedPortraitTaskBounds.height();
+
+        final Rect crop = mController.getCropBoundsIfNeeded(mainWindow);
+        assertNotEquals(null, crop);
+        assertEquals(0, crop.left);
+        assertEquals(0, crop.top);
+        assertEquals((int) (appWidth * scaling), crop.right);
+        assertEquals((int) ((appHeight - TASKBAR_EXPANDED_HEIGHT) * scaling), crop.bottom);
+    }
+
+    @Test
+    public void testGetRoundedCornersRadius_withRoundedCornersFromInsets() {
+        final float invGlobalScale = 0.5f;
+        final int expectedRadius = 7;
+        final int configurationRadius = 15;
+
+        final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
+        mainWindow.mInvGlobalScale = invGlobalScale;
+        final InsetsState insets = mainWindow.getInsetsState();
+
+        RoundedCorners roundedCorners = new RoundedCorners(
+                /*topLeft=*/ null,
+                /*topRight=*/ null,
+                /*bottomRight=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT,
+                    configurationRadius, /*centerX=*/ 1, /*centerY=*/ 1),
+                /*bottomLeft=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT,
+                    configurationRadius * 2 /*2 is to test selection of the min radius*/,
+                    /*centerX=*/ 1, /*centerY=*/ 1)
+        );
+        doReturn(roundedCorners).when(insets).getRoundedCorners();
+        mLetterboxConfiguration.setLetterboxActivityCornersRadius(-1);
+
+        assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+    }
+
+    @Test
+    public void testGetRoundedCornersRadius_withLetterboxActivityCornersRadius() {
+        final float invGlobalScale = 0.5f;
+        final int expectedRadius = 7;
+        final int configurationRadius = 15;
+
+        final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
+        mainWindow.mInvGlobalScale = invGlobalScale;
+        mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
+
+        assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+
+    }
+
+    @Test
+    public void testGetRoundedCornersRadius_noScalingApplied() {
+        final int configurationRadius = 15;
+
+        final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
+        mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
+
+        mainWindow.mInvGlobalScale = -1f;
+        assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow));
+
+        mainWindow.mInvGlobalScale = 0f;
+        assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow));
+
+        mainWindow.mInvGlobalScale = 1f;
+        assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow));
+    }
+
+    private WindowState mockForGetCropBoundsAndRoundedCorners(@Nullable InsetsSource taskbar) {
+        final WindowState mainWindow = mock(WindowState.class);
+        final InsetsState insets = mock(InsetsState.class);
+        final Resources resources = mWm.mContext.getResources();
+        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+
+        mainWindow.mInvGlobalScale = 1f;
+        spyOn(resources);
+        spyOn(mActivity);
+
+        if (taskbar != null) {
+            taskbar.setVisible(true);
+            doReturn(taskbar).when(insets).peekSource(taskbar.getType());
+        }
+        doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
+        doReturn(true).when(mActivity).isVisible();
+        doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+        doReturn(insets).when(mainWindow).getInsetsState();
+        doReturn(attrs).when(mainWindow).getAttrs();
+        doReturn(true).when(mainWindow).isDrawn();
+        doReturn(false).when(mainWindow).isLetterboxedForDisplayCutout();
+        doReturn(true).when(mainWindow).areAppWindowBoundsLetterboxed();
+        doReturn(true).when(mLetterboxConfiguration).isLetterboxActivityCornersRounded();
+        doReturn(TASKBAR_EXPANDED_HEIGHT).when(resources).getDimensionPixelSize(
+                R.dimen.taskbar_frame_height);
+
+        // Need to reinitialise due to the change in resources getDimensionPixelSize output.
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        return mainWindow;
+    }
+
+    // overrideOrientationIfNeeded
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
+    public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
+            throws Exception {
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
+    public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsNosensor() {
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_NOSENSOR);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
+    public void testOverrideOrientationIfNeeded_nosensorOverride_orientationFixed_returnsUnchanged() {
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
+    public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationPortraitOrUndefined_returnsUnchanged() {
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
+    public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationLandscape_returnsReverseLandscape() {
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_LANDSCAPE),
+                SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
+    public void testOverrideOrientationIfNeeded_portraitOverride_orientationFixed_returnsUnchanged() {
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_NOSENSOR);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION})
+    public void testOverrideOrientationIfNeeded_portraitAndIgnoreFixedOverrides_returnsPortrait() {
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, OVERRIDE_ANY_ORIENTATION})
+    public void testOverrideOrientationIfNeeded_noSensorAndIgnoreFixedOverrides_returnsNosensor() {
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_NOSENSOR);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
+    public void testOverrideOrientationIfNeeded_propertyIsFalse_returnsUnchanged()
+            throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
+            OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+    public void testOverrideOrientationIfNeeded_whenCameraNotActive_returnsUnchanged() {
+        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+
+        // Recreate DisplayContent with DisplayRotationCompatPolicy
+        mActivity = setUpActivityWithComponent();
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
+        doReturn(false).when(mDisplayContent.mDisplayRotationCompatPolicy)
+                .isActivityEligibleForOrientationOverride(eq(mActivity));
+
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
+            OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA})
+    public void testOverrideOrientationIfNeeded_whenCameraActive_returnsPortrait() {
+        doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+
+        // Recreate DisplayContent with DisplayRotationCompatPolicy
+        mActivity = setUpActivityWithComponent();
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
+        doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
+                .isActivityEligibleForOrientationOverride(eq(mActivity));
+
+        assertEquals(mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    // shouldUseDisplayLandscapeNaturalOrientation
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+    public void testShouldUseDisplayLandscapeNaturalOrientation_override_returnsTrue() {
+        prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
+        assertTrue(mController.shouldUseDisplayLandscapeNaturalOrientation());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+    public void testShouldUseDisplayLandscapeNaturalOrientation_overrideAndFalseProperty_returnsFalse()
+            throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
+        assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+    public void testShouldUseDisplayLandscapeNaturalOrientation_portraitNaturalOrientation_returnsFalse() {
+        prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
+        doReturn(ORIENTATION_PORTRAIT).when(mDisplayContent).getNaturalOrientation();
+
+        assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+    public void testShouldUseDisplayLandscapeNaturalOrientation_disabledIgnoreOrientationRequest_returnsFalse() {
+        prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+    public void testShouldUseDisplayLandscapeNaturalOrientation_inMultiWindowMode_returnsFalse() {
+        prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
+
+        spyOn(mTask);
+        doReturn(true).when(mTask).inMultiWindowMode();
+
+        assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+    public void testShouldSendFakeFocus_overrideEnabled_returnsTrue() {
+        doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertTrue(mController.shouldSendFakeFocus());
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+    public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() {
+        doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldSendFakeFocus());
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+    public void testIsCompatFakeFocusEnabled_propertyDisabledAndOverrideEnabled_fakeFocusDisabled()
+            throws Exception {
+        doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+        mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldSendFakeFocus());
+    }
+
+    @Test
+    @DisableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
+    public void testIsCompatFakeFocusEnabled_propertyEnabled_noOverride_fakeFocusEnabled()
+            throws Exception {
+        doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+        mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertTrue(mController.shouldSendFakeFocus());
+    }
+
+    @Test
+    public void testIsCompatFakeFocusEnabled_propertyDisabled_fakeFocusDisabled()
+            throws Exception {
+        doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+        mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldSendFakeFocus());
+    }
+
+    @Test
+    public void testIsCompatFakeFocusEnabled_propertyEnabled_fakeFocusEnabled()
+            throws Exception {
+        doReturn(true).when(mLetterboxConfiguration).isCompatFakeFocusEnabled();
+        mockThatProperty(PROPERTY_COMPAT_ENABLE_FAKE_FOCUS, /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertTrue(mController.shouldSendFakeFocus());
+    }
+
     private void mockThatProperty(String propertyName, boolean value) throws Exception {
         Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
                  /* className */ "");
@@ -316,6 +740,12 @@
         doReturn(property).when(pm).getProperty(eq(propertyName), anyString());
     }
 
+    private void prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation() {
+        spyOn(mDisplayContent);
+        doReturn(ORIENTATION_LANDSCAPE).when(mDisplayContent).getNaturalOrientation();
+        mDisplayContent.setIgnoreOrientationRequest(true);
+    }
+
     private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() {
         doReturn(true).when(mLetterboxConfiguration)
                 .isPolicyForIgnoringRequestedOrientationEnabled();
@@ -325,10 +755,10 @@
     private ActivityRecord setUpActivityWithComponent() {
         mDisplayContent = new TestDisplayContent
                 .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build();
-        Task task = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build();
+        mTask = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build();
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setOnTop(true)
-                .setTask(task)
+                .setTask(mTask)
                 // Set the component to be that of the test class in order to enable compat changes
                 .setComponent(ComponentName.createRelative(mContext,
                         com.android.server.wm.LetterboxUiControllerTest.class.getName()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 4808474..06e3854 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -468,7 +468,7 @@
         mWm.setRecentsAnimationController(mController);
         spyOn(mDisplayContent.mFixedRotationTransitionListener);
         final ActivityRecord recents = mock(ActivityRecord.class);
-        recents.mOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+        recents.setOverrideOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
         doReturn(ORIENTATION_PORTRAIT).when(recents)
                 .getRequestedConfigurationOrientation(anyBoolean());
         mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recents);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 9d2eb26..63797778 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -21,7 +21,9 @@
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.os.Parcel;
@@ -258,6 +260,14 @@
         assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+
+        // If there will be display size change when switching from preferred mode to default mode,
+        // then keep the current preferred mode during animating.
+        mDisplayInfo = spy(mDisplayInfo);
+        final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE);
+        doReturn(defaultMode).when(mDisplayInfo).getDefaultMode();
+        mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
+        assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 995932c..fb5fda1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -16,8 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
@@ -53,8 +55,6 @@
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityRecord.State.STOPPED;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.LetterboxConfiguration.PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN;
-import static com.android.server.wm.LetterboxConfiguration.PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -98,7 +98,7 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.internal.statusbar.LetterboxDetails;
 import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.wm.DeviceStateController.FoldState;
+import com.android.server.wm.DeviceStateController.DeviceState;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -1876,6 +1876,43 @@
     }
 
     @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
+    public void testOverrideRespectRequestedOrientationIsEnabled_orientationIsRespected() {
+        // Set up a display in landscape
+        setUpDisplaySizeWithApp(2800, 1400);
+
+        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+                RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        // Display should be rotated.
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation());
+
+        // No size compat mode
+        assertFalse(activity.inSizeCompatMode());
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
+    public void testOverrideRespectRequestedOrientationIsEnabled_multiWindow_orientationIgnored() {
+        // Set up a display in landscape
+        setUpDisplaySizeWithApp(2800, 1400);
+
+        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+                RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+        TaskFragment taskFragment = activity.getTaskFragment();
+        spyOn(taskFragment);
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        doReturn(WINDOWING_MODE_MULTI_WINDOW).when(taskFragment).getWindowingMode();
+
+        // Display should not be rotated.
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation());
+
+        // No size compat mode
+        assertFalse(activity.inSizeCompatMode());
+    }
+
+    @Test
     public void testSplitAspectRatioForUnresizableLandscapeApps() {
         // Set up a display in portrait and ignoring orientation request.
         int screenWidth = 1400;
@@ -1912,6 +1949,132 @@
     }
 
     @Test
+    public void testDisplayAspectRatioForResizablePortraitApps() {
+        // Set up a display in portrait and ignoring orientation request.
+        int displayWidth = 1400;
+        int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(2f);
+
+        // Enable display aspect ratio to take precedence before
+        // fixedOrientationLetterboxAspectRatio
+        mWm.mLetterboxConfiguration
+                .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);
+
+        // Set up resizable app in portrait
+        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */);
+
+        final TestSplitOrganizer organizer =
+                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+        // Move activity to split screen which takes half of the screen.
+        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+        organizer.mPrimary.setBounds(0, 0, displayWidth, getExpectedSplitSize(displayHeight));
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+
+        // App should launch in fixed orientation letterbox.
+        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        // Checking that there is no size compat mode.
+        assertFitted();
+        // Check that the display aspect ratio is used by the app.
+        final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
+        final float delta = 0.01f;
+        assertEquals(targetMinAspectRatio, ActivityRecord
+                .computeAspectRatio(mActivity.getBounds()), delta);
+    }
+
+    @Test
+    public void testDisplayAspectRatioForResizableLandscapeApps() {
+        // Set up a display in landscape and ignoring orientation request.
+        int displayWidth = 1600;
+        int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(2f);
+
+        // Enable display aspect ratio to take precedence before
+        // fixedOrientationLetterboxAspectRatio
+        mWm.mLetterboxConfiguration
+                .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);
+
+        // Set up resizable app in landscape
+        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_LANDSCAPE, false /* isUnresizable */);
+
+        final TestSplitOrganizer organizer =
+                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+        // Move activity to split screen which takes half of the screen.
+        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+        organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(displayWidth), displayHeight);
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+
+        // App should launch in fixed orientation letterbox.
+        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        // Checking that there is no size compat mode.
+        assertFitted();
+        // Check that the display aspect ratio is used by the app.
+        final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
+        final float delta = 0.01f;
+        assertEquals(targetMinAspectRatio, ActivityRecord
+                .computeAspectRatio(mActivity.getBounds()), delta);
+    }
+
+    @Test
+    public void testDisplayAspectRatioForUnresizableLandscapeApps() {
+        // Set up a display in portrait and ignoring orientation request.
+        int displayWidth = 1400;
+        int displayHeight = 1600;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
+        // Enable display aspect ratio to take precedence before
+        // fixedOrientationLetterboxAspectRatio
+        mWm.mLetterboxConfiguration
+                .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);
+
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+        // App should launch in fixed orientation letterbox.
+        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        // Checking that there is no size compat mode.
+        assertFitted();
+        // Check that the display aspect ratio is used by the app.
+        final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
+        final float delta = 0.01f;
+        assertEquals(targetMinAspectRatio, ActivityRecord
+                .computeAspectRatio(mActivity.getBounds()), delta);
+    }
+
+    @Test
+    public void testDisplayAspectRatioForUnresizablePortraitApps() {
+        // Set up a display in landscape and ignoring orientation request.
+        int displayWidth = 1600;
+        int displayHeight = 1400;
+        setUpDisplaySizeWithApp(displayWidth, displayHeight);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
+        // Enable display aspect ratio to take precedence before
+        // fixedOrientationLetterboxAspectRatio
+        mWm.mLetterboxConfiguration
+                .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);
+
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        // App should launch in fixed orientation letterbox.
+        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+        // Checking that there is no size compat mode.
+        assertFitted();
+        // Check that the display aspect ratio is used by the app.
+        final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
+        final float delta = 0.01f;
+        assertEquals(targetMinAspectRatio, ActivityRecord
+                .computeAspectRatio(mActivity.getBounds()), delta);
+    }
+
+    @Test
     public void
             testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() {
         // Set up a display in landscape and ignoring orientation request.
@@ -2187,6 +2350,29 @@
     }
 
     @Test
+    public void testDisplayIgnoreOrientationRequest_disabledViaDeviceConfig_orientationRespected() {
+        // Set up a display in landscape
+        setUpDisplaySizeWithApp(2800, 1400);
+
+        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+                RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+        spyOn(activity.mWmService.mLetterboxConfiguration);
+        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
+                .isIgnoreOrientationRequestAllowed();
+
+        // Display should not be rotated.
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation());
+
+        doReturn(false).when(activity.mWmService.mLetterboxConfiguration)
+                .isIgnoreOrientationRequestAllowed();
+
+        // Display should be rotated.
+        assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation());
+    }
+
+    @Test
     public void testSandboxDisplayApis_unresizableAppNotSandboxed() {
         // Set up a display in landscape with an unresizable app.
         setUpDisplaySizeWithApp(2500, 1000);
@@ -2458,6 +2644,133 @@
     }
 
     @Test
+    public void testIsHorizontalReachabilityEnabled_splitScreen_false() {
+        mAtm.mDevEnableNonResizableMultiWindow = true;
+        setUpDisplaySizeWithApp(2800, 1000);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+        final TestSplitOrganizer organizer =
+                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+        // Unresizable portrait-only activity.
+        prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT);
+
+        // Move activity to split screen which takes half of the screen.
+        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+        organizer.mPrimary.setBounds(0, 0, 1400, 1000);
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+
+        // Horizontal reachability is disabled because the app is in split screen.
+        assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+    }
+
+    @Test
+    public void testIsVerticalReachabilityEnabled_splitScreen_false() {
+        mAtm.mDevEnableNonResizableMultiWindow = true;
+        setUpDisplaySizeWithApp(1000, 2800);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+        final TestSplitOrganizer organizer =
+                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+        // Unresizable landscape-only activity.
+        prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE);
+
+        // Move activity to split screen which takes half of the screen.
+        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+        organizer.mPrimary.setBounds(0, 0, 1000, 1400);
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+
+        // Vertical reachability is disabled because the app is in split screen.
+        assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+    }
+
+    @Test
+    public void testIsVerticalReachabilityEnabled_doesNotMatchParentWidth_false() {
+        setUpDisplaySizeWithApp(1000, 2800);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+
+        // Unresizable landscape-only activity.
+        prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE);
+
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        // Activity now in size compat mode.
+        assertTrue(mActivity.inSizeCompatMode());
+
+        // Vertical reachability is disabled because the app does not match parent width
+        assertNotEquals(mActivity.getBounds().width(), mActivity.mDisplayContent.getBounds()
+                .width());
+        assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+    }
+
+    @Test
+    public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() {
+        setUpDisplaySizeWithApp(2800, 1000);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+
+        // Unresizable portrait-only activity.
+        prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT);
+
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        // Activity now in size compat mode.
+        assertTrue(mActivity.inSizeCompatMode());
+
+        // Horizontal reachability is disabled because the app does not match parent height
+        assertNotEquals(mActivity.getBounds().height(), mActivity.mDisplayContent.getBounds()
+                .height());
+        assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+    }
+
+    @Test
+    public void testIsHorizontalReachabilityEnabled_inSizeCompatMode_matchesParentHeight_true() {
+        setUpDisplaySizeWithApp(1800, 2200);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+
+        // Unresizable portrait-only activity.
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        // Activity now in size compat mode.
+        assertTrue(mActivity.inSizeCompatMode());
+
+        // Horizontal reachability is enabled because the app matches parent height
+        assertEquals(mActivity.getBounds().height(), mActivity.mDisplayContent.getBounds()
+                .height());
+        assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+    }
+
+    @Test
+    public void testIsVerticalReachabilityEnabled_inSizeCompatMode_matchesParentWidth_true() {
+        setUpDisplaySizeWithApp(2200, 1800);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+
+        // Unresizable landscape-only activity.
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        // Activity now in size compat mode.
+        assertTrue(mActivity.inSizeCompatMode());
+
+        // Vertical reachability is enabled because the app matches parent width
+        assertEquals(mActivity.getBounds().width(), mActivity.mDisplayContent.getBounds().width());
+        assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+    }
+
+    @Test
     public void testLetterboxDetailsForStatusBar_noLetterbox() {
         setUpDisplaySizeWithApp(2800, 1000);
         addStatusBar(mActivity.mDisplayContent);
@@ -2568,7 +2881,7 @@
         mActivity.mRootWindowContainer.performSurfacePlacement();
 
         final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class);
-        verify(mTransaction, times(2)).setWindowCrop(
+        verify(mTransaction, times(2)).setCrop(
                 eq(w1.getSurfaceControl()),
                 cropCapturer.capture()
         );
@@ -2657,6 +2970,39 @@
     }
 
     @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_leftInsets_appCentered() {
+        // Set up folded display
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1100, 2100)
+                .setCanRotate(true)
+                .build();
+        display.setIgnoreOrientationRequest(true);
+        final DisplayPolicy policy = display.getDisplayPolicy();
+        DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
+                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+        decorInfo.mNonDecorInsets.set(130, 0,  60, 0);
+        spyOn(policy);
+        doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
+                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+        mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        // Resize the display to simulate unfolding in portrait
+        resizeDisplay(mTask.mDisplayContent, 2200, 1800);
+        assertTrue(mActivity.inSizeCompatMode());
+
+        // Simulate real display not taking non-decor insets into consideration
+        display.getWindowConfiguration().setAppBounds(0, 0, 2200, 1800);
+
+        // Rotate display to landscape
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        // App is centered
+        assertEquals(mActivity.getBounds(), new Rect(350, 50, 1450, 2150));
+    }
+
+    @Test
     public void testUpdateResolvedBoundsHorizontalPosition_left() {
         // Display configured as (2800, 1400).
         assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
@@ -2802,6 +3148,20 @@
     }
 
     @Test
+    public void testApplyAspectRatio_containingRatioAlmostEqualToMaxRatio_boundsUnchanged() {
+        setUpDisplaySizeWithApp(1981, 2576);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+        final Rect originalBounds = new Rect(mActivity.getBounds());
+        prepareUnresizable(mActivity, 1.3f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+        // The containing aspect ratio is now 1.3003534, while the desired aspect ratio is 1.3. The
+        // bounds of the activity should not be changed as the difference is too small
+        assertEquals(mActivity.getBounds(), originalBounds);
+    }
+
+    @Test
     public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
         // When activity width equals parent width, multiplier shouldn't have any effect.
         assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
@@ -2813,6 +3173,39 @@
     }
 
     @Test
+    public void testUpdateResolvedBoundsVerticalPosition_topInsets_appCentered() {
+        // Set up folded display
+        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2100, 1100)
+                .setCanRotate(true)
+                .build();
+        display.setIgnoreOrientationRequest(true);
+        final DisplayPolicy policy = display.getDisplayPolicy();
+        DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
+                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+        decorInfo.mNonDecorInsets.set(0, 130,  0, 60);
+        spyOn(policy);
+        doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
+                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
+        mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+
+        setUpApp(display);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+        // Resize the display to simulate unfolding in portrait
+        resizeDisplay(mTask.mDisplayContent, 1800, 2200);
+        assertTrue(mActivity.inSizeCompatMode());
+
+        // Simulate real display not taking non-decor insets into consideration
+        display.getWindowConfiguration().setAppBounds(0, 0, 1800, 2200);
+
+        // Rotate display to landscape
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+        // App is centered
+        assertEquals(mActivity.getBounds(), new Rect(50, 350, 2150, 1450));
+    }
+
+    @Test
     public void testUpdateResolvedBoundsVerticalPosition_top() {
         // Display configured as (1400, 2800).
         assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
@@ -2933,9 +3326,9 @@
     private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) {
         final DisplayRotation r = mActivity.mDisplayContent.getDisplayRotation();
         doReturn(isHalfFolded).when(r).isDisplaySeparatingHinge();
-        doReturn(false).when(r).isDeviceInPosture(any(FoldState.class), anyBoolean());
+        doReturn(false).when(r).isDeviceInPosture(any(DeviceState.class), anyBoolean());
         if (isHalfFolded) {
-            doReturn(true).when(r).isDeviceInPosture(FoldState.HALF_FOLDED, isTabletop);
+            doReturn(true).when(r).isDeviceInPosture(DeviceState.HALF_FOLDED, isTabletop);
         }
         mActivity.recomputeConfiguration();
     }
@@ -3282,7 +3675,8 @@
         assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
     }
 
-    private ActivityRecord setUpActivityForCompatFakeFocusTest() {
+    @Test
+    public void testShouldSendFakeFocus_compatFakeFocusEnabled() {
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setOnTop(true)
@@ -3291,69 +3685,40 @@
                         com.android.server.wm.SizeCompatTests.class.getName()))
                 .build();
         final Task task = activity.getTask();
+        spyOn(activity.mLetterboxUiController);
+        doReturn(true).when(activity.mLetterboxUiController).shouldSendFakeFocus();
+
         task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        spyOn(activity.mWmService.mLetterboxConfiguration);
-        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
-                .isCompatFakeFocusEnabledOnDevice();
-        return activity;
-    }
-
-    @Test
-    @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
-    public void testShouldSendFakeFocus_overrideEnabled_returnsTrue() {
-        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
-
         assertTrue(activity.shouldSendCompatFakeFocus());
-    }
 
-    @Test
-    @DisableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
-    public void testShouldSendFakeFocus_overrideDisabled_returnsFalse() {
-        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
+        task.setWindowingMode(WINDOWING_MODE_PINNED);
+        assertFalse(activity.shouldSendCompatFakeFocus());
 
+        task.setWindowingMode(WINDOWING_MODE_FREEFORM);
         assertFalse(activity.shouldSendCompatFakeFocus());
     }
 
     @Test
-    @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
-    public void testIsCompatFakeFocusEnabled_optOutPropertyAndOverrideEnabled_fakeFocusDisabled() {
-        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
-        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
-                .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT));
+    public void testShouldSendFakeFocus_compatFakeFocusDisabled() {
+        final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setCreateTask(true)
+                .setOnTop(true)
+                // Set the component to be that of the test class in order to enable compat changes
+                .setComponent(ComponentName.createRelative(mContext,
+                        com.android.server.wm.SizeCompatTests.class.getName()))
+                .build();
+        final Task task = activity.getTask();
+        spyOn(activity.mLetterboxUiController);
+        doReturn(false).when(activity.mLetterboxUiController).shouldSendFakeFocus();
 
-        assertFalse(activity.mWmService.mLetterboxConfiguration
-                .isCompatFakeFocusEnabled(activity.info));
-    }
+        task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        assertFalse(activity.shouldSendCompatFakeFocus());
 
-    @Test
-    @DisableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
-    public void testIsCompatFakeFocusEnabled_optInPropertyEnabled_noOverride_fakeFocusEnabled() {
-        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
-        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
-                .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN));
+        task.setWindowingMode(WINDOWING_MODE_PINNED);
+        assertFalse(activity.shouldSendCompatFakeFocus());
 
-        assertTrue(activity.mWmService.mLetterboxConfiguration
-                .isCompatFakeFocusEnabled(activity.info));
-    }
-
-    @Test
-    public void testIsCompatFakeFocusEnabled_optOutPropertyEnabled_fakeFocusDisabled() {
-        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
-        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
-                .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT));
-
-        assertFalse(activity.mWmService.mLetterboxConfiguration
-                .isCompatFakeFocusEnabled(activity.info));
-    }
-
-    @Test
-    public void testIsCompatFakeFocusEnabled_optInPropertyEnabled_fakeFocusEnabled() {
-        ActivityRecord activity = setUpActivityForCompatFakeFocusTest();
-        doReturn(true).when(activity.mWmService.mLetterboxConfiguration)
-                .getPackageManagerProperty(any(), eq(PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN));
-
-        assertTrue(activity.mWmService.mLetterboxConfiguration
-                .isCompatFakeFocusEnabled(activity.info));
+        task.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertFalse(activity.shouldSendCompatFakeFocus());
     }
 
     private int getExpectedSplitSize(int dimensionToSplit) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index bf1d1fa..83be4f0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.view.Surface.ROTATION_0;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -78,6 +79,12 @@
         final InputMonitor inputMonitor = getInputMonitor();
         spyOn(inputMonitor);
         doNothing().when(inputMonitor).resumeDispatchingLw(any());
+
+        // For devices that set the sysprop ro.bootanim.set_orientation_<display_id>
+        // See DisplayRotation#readDefaultDisplayRotation for context.
+        // Without that, meaning of height and width in context of the tests can be swapped if
+        // the default rotation is 90 or 270.
+        displayRotation.setRotation(ROTATION_0);
     }
 
     public static class Builder {
@@ -203,6 +210,7 @@
             }
 
             final int displayId = SystemServicesTestRule.sNextDisplayId++;
+            mInfo.displayId = displayId;
             final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
                     mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
             final TestDisplayContent newDisplay = createInternal(display);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index ed7d123..2446fc4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -1557,7 +1557,7 @@
 
         @Override
         int getOrientation() {
-            return getOrientation(super.mOrientation);
+            return getOrientation(super.getOverrideOrientation());
         }
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index fd3776f..514aec1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -1138,7 +1138,9 @@
         spyOn(app.getDisplayContent());
         app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
 
-        verify(app.getDisplayContent()).updateImeControlTarget();
+        // Expect updateImeParent will be invoked when the configuration of the IME control
+        // target has changed.
+        verify(app.getDisplayContent()).updateImeControlTarget(eq(true) /* updateImeParent */);
         assertEquals(mAppWindow, mDisplayContent.getImeTarget(IME_TARGET_CONTROL).getWindow());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 268aa3e..f8b8094 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -253,6 +253,12 @@
         // device form factors.
         mAtm.mWindowManager.mLetterboxConfiguration
                 .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(false);
+        // Ensure aspect ratio for al apps isn't overridden on any device target.
+        // {@link com.android.internal.R.bool
+        // .config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled}, may be set on
+        // some device form factors.
+        mAtm.mWindowManager.mLetterboxConfiguration
+                .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(false);
 
         checkDeviceSpecificOverridesNotApplied();
     }
@@ -267,6 +273,8 @@
         mAtm.mWindowManager.mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
         mAtm.mWindowManager.mLetterboxConfiguration
                 .resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+        mAtm.mWindowManager.mLetterboxConfiguration
+                .resetIsDisplayAspectRatioEnabledForFixedOrientationLetterbox();
     }
 
     /**
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 42a5af7..17c354a 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -36,6 +36,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -106,6 +107,12 @@
         return false;
     }
 
+    /**
+     * List of connected MIDI devices
+     */
+    private final HashMap<String, UsbMidiDevice>
+            mMidiDevices = new HashMap<String, UsbMidiDevice>();
+
     // UsbMidiDevice for USB peripheral mode (gadget) device
     private UsbMidiDevice mPeripheralMidiDevice = null;
 
@@ -249,6 +256,8 @@
             }
         }
 
+        addMidiDevice(deviceAddress, usbDevice, parser, cardRec);
+
         logDevices("deviceAdded()");
 
         if (DEBUG) {
@@ -256,6 +265,54 @@
         }
     }
 
+    private void addMidiDevice(String deviceAddress, UsbDevice usbDevice,
+            UsbDescriptorParser parser, AlsaCardsParser.AlsaCardRecord cardRec) {
+        boolean hasMidi = parser.hasMIDIInterface();
+        // UsbHostManager will create UsbDirectMidiDevices instead if MIDI 2 is supported.
+        boolean hasMidi2 = parser.containsUniversalMidiDeviceEndpoint();
+        if (DEBUG) {
+            Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
+            Slog.d(TAG, "hasMidi2: " + hasMidi2);
+        }
+        if (mHasMidiFeature && hasMidi && !hasMidi2) {
+            Bundle properties = new Bundle();
+            String manufacturer = usbDevice.getManufacturerName();
+            String product = usbDevice.getProductName();
+            String version = usbDevice.getVersion();
+            String name;
+            if (manufacturer == null || manufacturer.isEmpty()) {
+                name = product;
+            } else if (product == null || product.isEmpty()) {
+                name = manufacturer;
+            } else {
+                name = manufacturer + " " + product;
+            }
+            properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
+            properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
+            properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
+            properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
+            properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
+                    usbDevice.getSerialNumber());
+            properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum());
+            properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/);
+            properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
+
+            int numLegacyMidiInputs = parser.calculateNumLegacyMidiInputs();
+            int numLegacyMidiOutputs = parser.calculateNumLegacyMidiOutputs();
+            if (DEBUG) {
+                Slog.d(TAG, "numLegacyMidiInputs: " + numLegacyMidiInputs);
+                Slog.d(TAG, "numLegacyMidiOutputs:" + numLegacyMidiOutputs);
+            }
+
+            UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
+                    cardRec.getCardNum(), 0 /*device*/, numLegacyMidiInputs,
+                    numLegacyMidiOutputs);
+            if (usbMidiDevice != null) {
+                mMidiDevices.put(deviceAddress, usbMidiDevice);
+            }
+        }
+    }
+
     /* package */ synchronized void usbDeviceRemoved(String deviceAddress/*UsbDevice usbDevice*/) {
         if (DEBUG) {
             Slog.d(TAG, "deviceRemoved(" + deviceAddress + ")");
@@ -269,6 +326,13 @@
             selectDefaultDevice(); // if there any external devices left, select one of them
         }
 
+        // MIDI
+        UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
+        if (usbMidiDevice != null) {
+            Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress);
+            IoUtils.closeQuietly(usbMidiDevice);
+        }
+
         logDevices("usbDeviceRemoved()");
 
     }
@@ -324,6 +388,12 @@
             usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES);
         }
 
+        for (String deviceAddr : mMidiDevices.keySet()) {
+            // A UsbMidiDevice does not have a handle to the UsbDevice anymore
+            mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
+                    UsbAlsaManagerProto.MIDI_DEVICES);
+        }
+
         dump.end(token);
     }
 
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index f389276..b3eb285 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -444,14 +444,19 @@
                         } else {
                             Slog.e(TAG, "Universal Midi Device is null.");
                         }
-                    }
-                    if (parser.containsLegacyMidiDeviceEndpoint()) {
-                        UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
-                                newDevice, parser, false, uniqueUsbDeviceIdentifier);
-                        if (midiDevice != null) {
-                            midiDevices.add(midiDevice);
-                        } else {
-                            Slog.e(TAG, "Legacy Midi Device is null.");
+
+                        // Use UsbDirectMidiDevice only if this supports MIDI 2.0 as well.
+                        // ALSA removes the audio sound card if MIDI interfaces are removed.
+                        // This means that as long as ALSA is used for audio, MIDI 1.0 USB
+                        // devices should use the ALSA path for MIDI.
+                        if (parser.containsLegacyMidiDeviceEndpoint()) {
+                            midiDevice = UsbDirectMidiDevice.create(mContext,
+                                    newDevice, parser, false, uniqueUsbDeviceIdentifier);
+                            if (midiDevice != null) {
+                                midiDevices.add(midiDevice);
+                            } else {
+                                Slog.e(TAG, "Legacy Midi Device is null.");
+                            }
                         }
                     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index 3f2d8c8..c6ea228 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -79,6 +79,10 @@
         mInterfaceDescriptors.add(interfaceDesc);
     }
 
+    ArrayList<UsbInterfaceDescriptor> getInterfaceDescriptors() {
+        return mInterfaceDescriptors;
+    }
+
     private boolean isAudioInterface(UsbInterfaceDescriptor descriptor) {
         return descriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO
                 && descriptor.getUsbSubclass() == UsbDescriptor.AUDIO_AUDIOSTREAMING;
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index cd6ea68..626ce89 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -40,6 +40,7 @@
     private UsbDeviceDescriptor mDeviceDescriptor;
     private UsbConfigDescriptor mCurConfigDescriptor;
     private UsbInterfaceDescriptor mCurInterfaceDescriptor;
+    private UsbEndpointDescriptor mCurEndpointDescriptor;
 
     // The AudioClass spec implemented by the AudioClass Interfaces
     // This may well be different than the overall USB Spec.
@@ -165,7 +166,7 @@
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
-                descriptor = new UsbEndpointDescriptor(length, type);
+                descriptor = mCurEndpointDescriptor = new UsbEndpointDescriptor(length, type);
                 if (mCurInterfaceDescriptor != null) {
                     mCurInterfaceDescriptor.addEndpointDescriptor(
                             (UsbEndpointDescriptor) descriptor);
@@ -265,6 +266,9 @@
                                     + Integer.toHexString(subClass));
                             break;
                     }
+                    if (mCurEndpointDescriptor != null && descriptor != null) {
+                        mCurEndpointDescriptor.setClassSpecificEndpointDescriptor(descriptor);
+                    }
                 }
                 break;
 
@@ -798,6 +802,84 @@
     /**
      * @hide
      */
+    private int calculateNumLegacyMidiPorts(boolean isOutput) {
+        // Only look at the first config.
+        UsbConfigDescriptor configDescriptor = null;
+        for (UsbDescriptor descriptor : mDescriptors) {
+            if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CONFIG) {
+                if (descriptor instanceof UsbConfigDescriptor) {
+                    configDescriptor = (UsbConfigDescriptor) descriptor;
+                    break;
+                } else {
+                    Log.w(TAG, "Unrecognized Config l: " + descriptor.getLength()
+                            + " t:0x" + Integer.toHexString(descriptor.getType()));
+                }
+            }
+        }
+        if (configDescriptor == null) {
+            Log.w(TAG, "Config not found");
+            return 0;
+        }
+
+        ArrayList<UsbInterfaceDescriptor> legacyMidiInterfaceDescriptors =
+                new ArrayList<UsbInterfaceDescriptor>();
+        for (UsbInterfaceDescriptor interfaceDescriptor
+                : configDescriptor.getInterfaceDescriptors()) {
+            if (interfaceDescriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO) {
+                if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
+                    UsbDescriptor midiHeaderDescriptor =
+                            interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
+                    if (midiHeaderDescriptor != null) {
+                        if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
+                            UsbMSMidiHeader midiHeader =
+                                    (UsbMSMidiHeader) midiHeaderDescriptor;
+                            if (midiHeader.getMidiStreamingClass() == MS_MIDI_1_0) {
+                                legacyMidiInterfaceDescriptors.add(interfaceDescriptor);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        int count = 0;
+        for (UsbInterfaceDescriptor interfaceDescriptor : legacyMidiInterfaceDescriptors) {
+            for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
+                UsbEndpointDescriptor endpoint =
+                        interfaceDescriptor.getEndpointDescriptor(i);
+                // 0 is output, 1 << 7 is input.
+                if ((endpoint.getDirection() == 0) == isOutput) {
+                    UsbDescriptor classSpecificEndpointDescriptor =
+                            endpoint.getClassSpecificEndpointDescriptor();
+                    if (classSpecificEndpointDescriptor != null
+                            && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) {
+                        UsbACMidi10Endpoint midiEndpoint =
+                                (UsbACMidi10Endpoint) classSpecificEndpointDescriptor;
+                        count += midiEndpoint.getNumJacks();
+                    }
+                }
+            }
+        }
+        return count;
+    }
+
+    /**
+     * @hide
+     */
+    public int calculateNumLegacyMidiInputs() {
+        return calculateNumLegacyMidiPorts(false /*isOutput*/);
+    }
+
+    /**
+     * @hide
+     */
+    public int calculateNumLegacyMidiOutputs() {
+        return calculateNumLegacyMidiPorts(true /*isOutput*/);
+    }
+
+    /**
+     * @hide
+     */
     public float getInputHeadsetProbability() {
         if (hasMIDIInterface()) {
             return 0.0f;
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index ab07ce7..1f448ac 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -79,6 +79,8 @@
     private byte mRefresh;
     private byte mSyncAddress;
 
+    private UsbDescriptor mClassSpecificEndpointDescriptor;
+
     public UsbEndpointDescriptor(int length, byte type) {
         super(length, type);
         mHierarchyLevel = 4;
@@ -112,6 +114,14 @@
         return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION;
     }
 
+    void setClassSpecificEndpointDescriptor(UsbDescriptor descriptor) {
+        mClassSpecificEndpointDescriptor = descriptor;
+    }
+
+    UsbDescriptor getClassSpecificEndpointDescriptor() {
+        return mClassSpecificEndpointDescriptor;
+    }
+
     /**
     * Returns a UsbEndpoint that this UsbEndpointDescriptor is describing.
     */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index aacc17a4..3361502 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -113,4 +113,18 @@
         }
         return false
     }
+
+    fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) {
+        val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
+                "toggle_fixed_portrait_btn")), FIND_TIMEOUT)
+        require(button != null) {
+            "Button not found, this usually happens when the device " +
+                    "was left in an unknown state (e.g. Screen turned off)"
+        }
+        button.click()
+        mInstrumentation.waitForIdleSync()
+        // Ensure app relaunching transition finish and the IME has shown
+        wmHelper.waitForAppTransitionIdle()
+        wmHelper.waitImeShown()
+    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
new file mode 100644
index 0000000..3b3bce6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.server.wm.flicker.ime
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group2
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.traces.region.RegionSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test IME window shown on the app with fixing portrait orientation.
+ * To run this test: `atest FlickerTests:OpenImeWindowToFixedPortraitAppTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group2
+class OpenImeWindowToFixedPortraitAppTest (private val testSpec: FlickerTestParameter) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup {
+                eachRun {
+                    testApp.launchViaIntent(wmHelper)
+                    testApp.openIME(device, wmHelper)
+                    // Enable letterbox when the app calls setRequestedOrientation
+                    device.executeShellCommand("cmd window set-ignore-orientation-request true")
+                }
+            }
+            transitions {
+                testApp.toggleFixPortraitOrientation(wmHelper)
+            }
+            teardown {
+                eachRun {
+                    testApp.exit()
+                    device.executeShellCommand("cmd window set-ignore-orientation-request false")
+                }
+            }
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun imeLayerVisibleStart() {
+        testSpec.assertLayersStart {
+            this.isVisible(FlickerComponentName.IME)
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun imeLayerExistsEnd() {
+        testSpec.assertLayersEnd {
+            this.isVisible(FlickerComponentName.IME)
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun imeLayerVisibleRegionKeepsTheSame() {
+        var imeLayerVisibleRegionBeforeTransition: RegionSubject? = null
+        testSpec.assertLayersStart {
+            imeLayerVisibleRegionBeforeTransition = this.visibleRegion(FlickerComponentName.IME)
+        }
+        testSpec.assertLayersEnd {
+            this.visibleRegion(FlickerComponentName.IME)
+                    .coversExactly(imeLayerVisibleRegionBeforeTransition!!.region)
+        }
+    }
+
+    @Postsubmit
+    @Test
+    fun appWindowWithLetterboxCoversExactlyOnScreen() {
+        val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+        testSpec.assertLayersEnd {
+            this.visibleRegion(testApp.component, FlickerComponentName.LETTERBOX)
+                    .coversExactly(displayBounds)
+        }
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance()
+                    .getConfigNonRotationTests(
+                            supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270),
+                            supportedNavigationModes = listOf(
+                                    WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+                                    WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+                            )
+                    )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index b8ef195..efd80f2 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -45,7 +45,7 @@
              android:theme="@style/CutoutShortEdges"
              android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
              android:windowSoftInputMode="stateVisible"
-             android:configChanges="orientation|screenSize"
+             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
              android:label="ImeAppAutoFocus"
              android:exported="true">
             <intent-filter>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index baaf707..e71fe80 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -26,14 +26,27 @@
               android:layout_width="match_parent"
 	      android:imeOptions="flagNoExtractUi"
               android:inputType="text"/>
-    <Button
-        android:id="@+id/finish_activity_btn"
+    <LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="Finish activity" />
-    <Button
-        android:id="@+id/start_dialog_themed_activity_btn"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="Start dialog themed activity" />
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/finish_activity_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Finish activity" />
+        <Button
+            android:id="@+id/start_dialog_themed_activity_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Dialog activity" />
+        <ToggleButton
+            android:id="@+id/toggle_fixed_portrait_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textOn="Portrait (On)"
+            android:textOff="Portrait (Off)"
+        />
+    </LinearLayout>
 </LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
index bb200f1..7ee8deb 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
@@ -16,21 +16,29 @@
 
 package com.android.server.wm.flicker.testapp;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
 import android.content.Intent;
 import android.widget.Button;
 import android.widget.EditText;
+import android.widget.ToggleButton;
 
 public class ImeActivityAutoFocus extends ImeActivity {
-
     @Override
     protected void onStart() {
         super.onStart();
 
-        EditText editTextField = findViewById(R.id.plain_text_input);
-        editTextField.requestFocus();
-
         Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn);
         startThemedActivityButton.setOnClickListener(
                 button -> startActivity(new Intent(this, DialogThemedActivity.class)));
+
+        ToggleButton toggleFixedPortraitButton = findViewById(R.id.toggle_fixed_portrait_btn);
+        toggleFixedPortraitButton.setOnCheckedChangeListener(
+                (button, isChecked) -> setRequestedOrientation(
+                        isChecked ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_UNSPECIFIED));
+
+        EditText editTextField = findViewById(R.id.plain_text_input);
+        editTextField.requestFocus();
     }
 }