Merge "Camera: Add AE priority mode tags" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 7369b37..9fc350d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -20920,6 +20920,8 @@
     method public boolean hasPermission(android.hardware.usb.UsbDevice);
     method public boolean hasPermission(android.hardware.usb.UsbAccessory);
     method public android.os.ParcelFileDescriptor openAccessory(android.hardware.usb.UsbAccessory);
+    method @FlaggedApi("android.hardware.usb.flags.enable_accessory_stream_api") @NonNull public java.io.InputStream openAccessoryInputStream(@NonNull android.hardware.usb.UsbAccessory);
+    method @FlaggedApi("android.hardware.usb.flags.enable_accessory_stream_api") @NonNull public java.io.OutputStream openAccessoryOutputStream(@NonNull android.hardware.usb.UsbAccessory);
     method public android.hardware.usb.UsbDeviceConnection openDevice(android.hardware.usb.UsbDevice);
     method public void requestPermission(android.hardware.usb.UsbDevice, android.app.PendingIntent);
     method public void requestPermission(android.hardware.usb.UsbAccessory, android.app.PendingIntent);
@@ -21022,6 +21024,7 @@
     method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
     method public android.view.View onCreateInputView();
     method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype);
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean);
     method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]);
     method public boolean onEvaluateFullscreenMode();
     method @CallSuper public boolean onEvaluateInputViewShown();
@@ -22909,6 +22912,7 @@
     method public void onCryptoError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CryptoException);
     method public abstract void onError(@NonNull android.media.MediaCodec, @NonNull android.media.MediaCodec.CodecException);
     method public abstract void onInputBufferAvailable(@NonNull android.media.MediaCodec, int);
+    method @FlaggedApi("android.media.codec.subsession_metrics") public void onMetricsFlushed(@NonNull android.media.MediaCodec, @NonNull android.os.PersistableBundle);
     method public abstract void onOutputBufferAvailable(@NonNull android.media.MediaCodec, int, @NonNull android.media.MediaCodec.BufferInfo);
     method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void onOutputBuffersAvailable(@NonNull android.media.MediaCodec, int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>);
     method public abstract void onOutputFormatChanged(@NonNull android.media.MediaCodec, @NonNull android.media.MediaFormat);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8954f8e..70fbad0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1270,13 +1270,21 @@
 
   public class WallpaperManager {
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int);
+    method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int);
+    method @FlaggedApi("android.app.customization_packs_apis") public static int getOrientation(@NonNull android.graphics.Point);
     method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
+    method @FlaggedApi("android.app.customization_packs_apis") @Nullable public android.os.ParcelFileDescriptor getWallpaperFile(int, boolean);
     method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.app.wallpaper.WallpaperInstance getWallpaperInstance(int);
     method public void setDisplayOffset(android.os.IBinder, int, int);
+    method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull android.util.SparseArray<android.graphics.Rect>, boolean, int) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
     method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(allOf={android.Manifest.permission.SET_WALLPAPER_COMPONENT, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public boolean setWallpaperComponentWithDescription(@NonNull android.app.wallpaper.WallpaperDescription, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float);
+    field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_LANDSCAPE = 1; // 0x1
+    field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_PORTRAIT = 0; // 0x0
+    field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_SQUARE_LANDSCAPE = 3; // 0x3
+    field @FlaggedApi("android.app.customization_packs_apis") public static final int ORIENTATION_SQUARE_PORTRAIT = 2; // 0x2
   }
 
 }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3ca55b9..c8ecfa9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -535,9 +535,13 @@
   public class WallpaperManager {
     method @Nullable public android.graphics.Bitmap getBitmap();
     method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int);
+    method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull java.util.List<android.graphics.Point>, int, boolean);
+    method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull android.graphics.Point, @NonNull java.util.List<android.graphics.Point>, @Nullable java.util.Map<android.graphics.Point,android.graphics.Rect>);
     method public boolean isLockscreenLiveWallpaperEnabled();
     method @Nullable public android.graphics.Rect peekBitmapDimensions();
     method @Nullable public android.graphics.Rect peekBitmapDimensions(int);
+    method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setBitmapWithCrops(@Nullable android.graphics.Bitmap, @NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>, boolean, int) throws java.io.IOException;
+    method @FlaggedApi("com.android.window.flags.multi_crop") @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public int setStreamWithCrops(@NonNull java.io.InputStream, @NonNull java.util.Map<android.graphics.Point,android.graphics.Rect>, boolean, int) throws java.io.IOException;
     method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float);
     method public boolean shouldEnableWideColorGamut();
     method public boolean wallpaperSupportsWcg(int);
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index b06fb9e..233dc75 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -177,6 +177,13 @@
     }
 
     @Override
+    @NonNull
+    public SparseArray<Rect> getBitmapCrops(int which) {
+        unsupported();
+        return new SparseArray<>();
+    }
+
+    @Override
     public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
             @Nullable Map<Point, Rect> cropHints) {
         return unsupported();
@@ -358,8 +365,9 @@
 
 
     @Override
-    public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
-            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+    public int setStreamWithCrops(@NonNull InputStream bitmapData,
+            @NonNull SparseArray<Rect> cropHints, boolean allowBackup, @SetWallpaperFlags int which
+    ) throws IOException {
         return unsupportedInt();
     }
 
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index f693e9b..6449ea1 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -97,6 +97,16 @@
     List getBitmapCrops(in List<Point> displaySizes, int which, boolean originalBitmap, int userId);
 
     /**
+     * For a given user, if the wallpaper of the specified which is an ImageWallpaper, return
+     * a bundle which is a Map<Integer, Rect> containing the custom cropHints that were sent to
+     * setBitmapWithCrops or setStreamWithCrops. These crops are relative to the original bitmap.
+     * If the wallpaper isn't an ImageWallpaper, return null.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL)")
+    @SuppressWarnings(value={"untyped-collection"})
+    Bundle getCurrentBitmapCrops(int which, int userId);
+
+    /**
      * Return how a bitmap of a given size would be cropped for a given list of display sizes when
      * set with the given suggested crops.
      * @hide
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 479f3df..abb2dd4 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -19,9 +19,10 @@
 import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
 import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
 import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT;
+import static android.app.Flags.FLAG_CUSTOMIZATION_PACKS_APIS;
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
-import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 
 import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
 import static com.android.window.flags.Flags.multiCrop;
@@ -342,24 +343,32 @@
      * Portrait orientation of most screens
      * @hide
      */
+    @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+    @SystemApi
     public static final int ORIENTATION_PORTRAIT = 0;
 
     /**
      * Landscape orientation of most screens
      * @hide
      */
+    @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+    @SystemApi
     public static final int ORIENTATION_LANDSCAPE = 1;
 
     /**
      * Portrait orientation with similar width and height (e.g. the inner screen of a foldable)
      * @hide
      */
+    @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+    @SystemApi
     public static final int ORIENTATION_SQUARE_PORTRAIT = 2;
 
     /**
      * Landscape orientation with similar width and height (e.g. the inner screen of a foldable)
      * @hide
      */
+    @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+    @SystemApi
     public static final int ORIENTATION_SQUARE_LANDSCAPE = 3;
 
     /**
@@ -368,7 +377,9 @@
      * @return the corresponding {@link ScreenOrientation}.
      * @hide
      */
-    public static @ScreenOrientation int getOrientation(Point screenSize) {
+    @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+    @SystemApi
+    public static @ScreenOrientation int getOrientation(@NonNull Point screenSize) {
         float ratio = ((float) screenSize.x) / screenSize.y;
         // ratios between 3/4 and 4/3 are considered square
         return ratio >= 4 / 3f ? ORIENTATION_LANDSCAPE
@@ -1623,14 +1634,15 @@
      *                   If false, return areas relative to the cropped bitmap.
      * @return A List of Rect where the Rect is within the cropped/original bitmap, and corresponds
      *          to what is displayed. The Rect may have a larger width/height ratio than the screen
-     *          due to parallax. Return {@code null} if the wallpaper is not an ImageWallpaper.
-     *          Also return {@code null} when called with which={@link #FLAG_LOCK} if there is a
+     *          due to parallax. Return an empty list if the wallpaper is not an ImageWallpaper.
+     *          Also return an empty list when called with which={@link #FLAG_LOCK} if there is a
      *          shared home + lock wallpaper.
      * @hide
      */
     @FlaggedApi(FLAG_MULTI_CROP)
+    @TestApi
     @RequiresPermission(READ_WALLPAPER_INTERNAL)
-    @Nullable
+    @NonNull
     public List<Rect> getBitmapCrops(@NonNull List<Point> displaySizes,
             @SetWallpaperFlags int which, boolean originalBitmap) {
         checkExactlyOneWallpaperFlagSet(which);
@@ -1653,6 +1665,52 @@
     }
 
     /**
+     * For the current user, if the wallpaper of the specified destination is an ImageWallpaper,
+     * return the custom crops of the wallpaper, that have been provided for example via
+     * {@link #setStreamWithCrops}. These crops are relative to the original bitmap.
+     * <p>
+     * This method helps apps that change wallpapers provide an undo option. Calling
+     * {@link #setStreamWithCrops(InputStream, SparseArray, boolean, int)} with this SparseArray and
+     * the current original bitmap file, that can be obtained with {@link #getWallpaperFile(int,
+     * boolean)} with {@code getCropped=false}, will exactly lead to the current wallpaper state.
+     *
+     * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+     * @return A map from {{@link #ORIENTATION_PORTRAIT}, {@link #ORIENTATION_LANDSCAPE},
+     *          {@link #ORIENTATION_SQUARE_PORTRAIT}, {{@link #ORIENTATION_SQUARE_LANDSCAPE}}} to
+     *          Rect, representing the custom cropHints. The map can be empty and will only contains
+     *          entries for screen orientations for which a custom crop was provided. If no custom
+     *          crop is provided for an orientation, the system will infer the crop based on the
+     *          custom crops of the other orientations; or center-align the full image if no custom
+     *          crops are provided at all.
+     *          <p>
+     *          Return an empty map if the wallpaper is not an ImageWallpaper. Also return
+     *          an empty map when called with which={@link #FLAG_LOCK} if there is a shared
+     *          home + lock wallpaper.
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+    @SystemApi
+    @RequiresPermission(READ_WALLPAPER_INTERNAL)
+    @NonNull
+    public SparseArray<Rect> getBitmapCrops(@SetWallpaperFlags int which) {
+        checkExactlyOneWallpaperFlagSet(which);
+        try {
+            Bundle bundle = sGlobals.mService.getCurrentBitmapCrops(which, mContext.getUserId());
+            SparseArray<Rect> result = new SparseArray<>();
+            if (bundle == null) return result;
+            for (String key : bundle.keySet()) {
+                int intKey = Integer.parseInt(key);
+                Rect rect = bundle.getParcelable(key, Rect.class);
+                result.put(intKey, rect);
+            }
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * For preview purposes.
      * Return how a bitmap of a given size would be cropped for a given list of display sizes, if
      * it was set as wallpaper via {@link #setBitmapWithCrops(Bitmap, Map, boolean, int)} or
@@ -1664,7 +1722,8 @@
      * @hide
      */
     @FlaggedApi(FLAG_MULTI_CROP)
-    @Nullable
+    @TestApi
+    @NonNull
     public List<Rect> getBitmapCrops(@NonNull Point bitmapSize, @NonNull List<Point> displaySizes,
             @Nullable Map<Point, Rect> cropHints) {
         try {
@@ -1890,9 +1949,14 @@
      *    defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
      * @param getCropped If true the cropped file will be retrieved, if false the original will
      *                   be retrieved.
-     *
+     * @return A ParcelFileDescriptor for the wallpaper bitmap of the given destination, if it's an
+     *                   ImageWallpaper wallpaper. Return {@code null} if the wallpaper is not an
+     *                   ImageWallpaper. Also return {@code null} when called with
+     *                   which={@link #FLAG_LOCK} if there is a shared home + lock wallpaper.
      * @hide
      */
+    @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
+    @SystemApi
     @Nullable
     public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, boolean getCropped) {
         return getWallpaperFile(which, mContext.getUserId(), getCropped);
@@ -2371,7 +2435,6 @@
     /**
      * Version of setBitmap that defines how the wallpaper will be positioned for different
      * display sizes.
-     * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
      * @param cropHints map from screen dimensions to a sub-region of the image to display for those
      *                  dimensions. The {@code Rect} sub-region may have a larger width/height ratio
      *                  than the screen dimensions to apply a horizontal parallax effect. If the
@@ -2380,6 +2443,7 @@
      * @hide
      */
     @FlaggedApi(FLAG_MULTI_CROP)
+    @TestApi
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
     public int setBitmapWithCrops(@Nullable Bitmap fullImage, @NonNull Map<Point, Rect> cropHints,
             boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
@@ -2562,7 +2626,6 @@
     /**
      * Version of setStream that defines how the wallpaper will be positioned for different
      * display sizes.
-     * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
      * @param cropHints map from screen dimensions to a sub-region of the image to display for those
      *                  dimensions. The {@code Rect} sub-region may have a larger width/height ratio
      *                  than the screen dimensions to apply a horizontal parallax effect. If the
@@ -2571,9 +2634,11 @@
      * @hide
      */
     @FlaggedApi(FLAG_MULTI_CROP)
+    @TestApi
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
-    public int setStreamWithCrops(InputStream bitmapData, @NonNull Map<Point, Rect> cropHints,
-            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+    public int setStreamWithCrops(@NonNull InputStream bitmapData,
+            @NonNull Map<Point, Rect> cropHints, boolean allowBackup, @SetWallpaperFlags int which)
+            throws IOException {
         SparseArray<Rect> crops = new SparseArray<>();
         cropHints.forEach((k, v) -> crops.put(getOrientation(k), v));
         return setStreamWithCrops(bitmapData, crops, allowBackup, which);
@@ -2583,15 +2648,21 @@
      * Similar to {@link #setStreamWithCrops(InputStream, Map, boolean, int)}, but using
      * {@link ScreenOrientation} as keys of the cropHints map. Used for backup & restore, since
      * WallpaperBackupAgent stores orientations rather than the exact display size.
-     * Requires permission {@link android.Manifest.permission#SET_WALLPAPER}.
+     * @param bitmapData A stream containing the raw data to install as a wallpaper. This
+     *                  data can be in any format handled by {@link BitmapRegionDecoder}.
      * @param cropHints map from {@link ScreenOrientation} to a sub-region of the image to display
      *                  for that screen orientation.
+     * @param allowBackup {@code true} if the OS is permitted to back up this wallpaper
+     *     image for restore to a future device; {@code false} otherwise.
+     * @param which Flags indicating which wallpaper(s) to configure with the new imagery.
      * @hide
      */
     @FlaggedApi(FLAG_MULTI_CROP)
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
-    public int setStreamWithCrops(InputStream bitmapData, @NonNull SparseArray<Rect> cropHints,
-            boolean allowBackup, @SetWallpaperFlags int which) throws IOException {
+    public int setStreamWithCrops(@NonNull InputStream bitmapData,
+            @NonNull SparseArray<Rect> cropHints, boolean allowBackup, @SetWallpaperFlags int which)
+            throws IOException {
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperService not running");
             throw new RuntimeException(new DeadSystemException());
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index ee93870..6934e98 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -100,16 +100,6 @@
 }
 
 flag {
-  name: "visit_person_uri"
-  namespace: "systemui"
-  description: "Guards the security fix that ensures all URIs Person.java are valid"
-  bug: "281044385"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "notification_expansion_optional"
   namespace: "systemui"
   description: "Experiment to restore the pre-S behavior where standard notifications are not expandable unless they have actions."
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index bcb5b36..d5e696d 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -7,4 +7,12 @@
   namespace: "supervision"
   description: "Flag to enable the SupervisionService"
   bug: "340351729"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "supervision_api_on_wear"
+  is_exported: true
+  namespace: "supervision"
+  description: "Flag to enable the SupervisionService on Wear devices"
+  bug: "373358935"
+}
diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig
index 4b880d0..f750a84 100644
--- a/core/java/android/app/wallpaper.aconfig
+++ b/core/java/android/app/wallpaper.aconfig
@@ -22,3 +22,21 @@
   bug: "347235611"
   is_exported: true
 }
+
+flag {
+    name: "customization_packs_apis"
+    is_exported: true
+    namespace: "systemui"
+    description: "Move APIs related to bitmap and crops to @SystemApi."
+    bug: "372344184"
+}
+
+flag {
+  name: "accurate_wallpaper_downsampling"
+  namespace: "systemui"
+  description: "Accurate downsampling of wallpaper bitmap for high resolution images"
+  bug: "355665230"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 34c3f57..e9e8578 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -38,6 +38,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AttributeSet;
+import android.util.EmptyArray;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -565,10 +566,7 @@
                                     usesSdkLibrariesVersionsMajor, usesSdkLibVersionMajor,
                                     /*allowDuplicates=*/ true);
 
-                            // We allow ":" delimiters in the SHA declaration as this is the format
-                            // emitted by the certtool making it easy for developers to copy/paste.
-                            // TODO(372862145): Add test for this replacement
-                            usesSdkCertDigest = usesSdkCertDigest.replace(":", "").toLowerCase();
+                            usesSdkCertDigest = normalizeCertDigest(usesSdkCertDigest);
 
                             if ("".equals(usesSdkCertDigest)) {
                                 // Test-only uses-sdk-library empty certificate digest override.
@@ -618,18 +616,23 @@
                                     usesStaticLibrariesVersions, usesStaticLibVersion,
                                     /*allowDuplicates=*/ true);
 
-                            // We allow ":" delimiters in the SHA declaration as this is the format
-                            // emitted by the certtool making it easy for developers to copy/paste.
-                            // TODO(372862145): Add test for this replacement
-                            usesStaticLibCertDigest =
-                                    usesStaticLibCertDigest.replace(":", "").toLowerCase();
+                            usesStaticLibCertDigest = normalizeCertDigest(usesStaticLibCertDigest);
 
-                            // TODO(372862145): Add support for multiple signer for app targeting
-                            //  O-MR1
+                            ParseResult<String[]> certResult =
+                                    parseAdditionalCertificates(input, parser);
+                            if (certResult.isError()) {
+                                return input.error(certResult);
+                            }
+                            String[] additionalCertSha256Digests = certResult.getResult();
+                            String[] certSha256Digests =
+                                    new String[additionalCertSha256Digests.length + 1];
+                            certSha256Digests[0] = usesStaticLibCertDigest;
+                            System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests,
+                                    1, additionalCertSha256Digests.length);
+
                             usesStaticLibrariesCertDigests = ArrayUtils.appendElement(
                                     String[].class, usesStaticLibrariesCertDigests,
-                                    new String[]{usesStaticLibCertDigest},
-                                    /*allowDuplicates=*/ true);
+                                    certSha256Digests, /*allowDuplicates=*/ true);
                             break;
                         case TAG_SDK_LIBRARY:
                             isSdkLibrary = true;
@@ -809,6 +812,43 @@
                         declaredLibraries));
     }
 
+    private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input,
+            XmlResourceParser parser) throws XmlPullParserException, IOException {
+        String[] certSha256Digests = EmptyArray.STRING;
+        final int depth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG
+                || parser.getDepth() > depth)) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+            final String nodeName = parser.getName();
+            if (nodeName.equals("additional-certificate")) {
+                String certSha256Digest = parser.getAttributeValue(
+                        ANDROID_RES_NAMESPACE, "certDigest");
+                if (TextUtils.isEmpty(certSha256Digest)) {
+                    return input.error("Bad additional-certificate declaration with empty"
+                            + " certDigest:" + certSha256Digest);
+                }
+
+                certSha256Digest = normalizeCertDigest(certSha256Digest);
+                certSha256Digests = ArrayUtils.appendElement(String.class,
+                        certSha256Digests, certSha256Digest);
+            }
+        }
+
+        return input.success(certSha256Digests);
+    }
+
+    /**
+     * We allow ":" delimiters in the SHA declaration as this is the format emitted by the
+     * certtool making it easy for developers to copy/paste.
+     */
+    private static String normalizeCertDigest(String certDigest) {
+        return certDigest.replace(":", "").toLowerCase();
+    }
+
     private static boolean isDeviceAdminReceiver(
             XmlResourceParser parser, boolean applicationHasBindDeviceAdminPermission)
             throws XmlPullParserException, IOException {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index b785630..75e2058 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -994,7 +994,7 @@
         AttributionSourceState contextAttributionSourceState =
                 contextAttributionSource.asState();
 
-        if (Flags.useContextAttributionSource() && useContextAttributionSource) {
+        if (Flags.dataDeliveryPermissionChecks() && useContextAttributionSource) {
             return contextAttributionSourceState;
         } else {
             AttributionSourceState clientAttribution =
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index e583627..8b4d0da 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -172,6 +172,23 @@
      */
     public static final int PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT = 17;
 
+    /**
+     * Property that indicates that this state corresponds to the device state for rear display
+     * mode, where both the inner and outer displays are on. In this state, the outer display
+     * is the default display where the app is shown, and the inner display is used by the system to
+     * show a UI affordance for exiting the mode.
+     *
+     * Note that this value should generally not be used, and may be removed in the future (e.g.
+     * if or when it becomes the only type of rear display mode when
+     * {@link android.hardware.devicestate.feature.flags.Flags#deviceStateRdmV2} is removed).
+     *
+     * As such, clients should strongly consider relying on {@link #PROPERTY_FEATURE_REAR_DISPLAY}
+     * instead.
+     *
+     * @hide
+     */
+    public static final int PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT = 1001;
+
     /** @hide */
     @IntDef(prefix = {"PROPERTY_"}, flag = false, value = {
             PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
@@ -190,7 +207,8 @@
             PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE,
             PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY,
             PROPERTY_FEATURE_REAR_DISPLAY,
-            PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT
+            PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT,
+            PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT
     })
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig
index 98ba9192..6230f4d 100644
--- a/core/java/android/hardware/devicestate/feature/flags.aconfig
+++ b/core/java/android/hardware/devicestate/feature/flags.aconfig
@@ -29,4 +29,13 @@
     metadata {
       purpose: PURPOSE_BUGFIX
     }
+}
+
+flag {
+    name: "device_state_rdm_v2"
+    is_exported: true
+    namespace: "windowing_sdk"
+    description: "Enables Rear Display Mode V2, where the inner display shows the user a UI affordance for exiting the state"
+    bug: "372486634"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index a8eb11d..4b2f2c2 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -153,8 +153,8 @@
 
 flag {
   name: "override_power_key_behavior_in_focused_window"
-  namespace: "input_native"
-  description: "Allows privileged focused windows to capture power key events."
+  namespace: "wallet_integration"
+  description: "Allows privileged focused windows to override the power key double tap behavior."
   bug: "357144512"
 }
 
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 92608d0..d2e232a 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -54,6 +54,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -823,6 +828,216 @@
         }
     }
 
+    /**
+     * Opens the handle for accessory, marks it as input or output, and adds it to the map
+     * if it is the first time the accessory has had an I/O stream associated with it.
+     */
+    private AccessoryHandle openHandleForAccessory(UsbAccessory accessory,
+            boolean openingInputStream)
+            throws RemoteException {
+        synchronized (mAccessoryHandleMapLock) {
+            if (mAccessoryHandleMap == null) {
+                mAccessoryHandleMap = new ArrayMap<>();
+            }
+
+            // If accessory isn't available in map
+            if (!mAccessoryHandleMap.containsKey(accessory)) {
+                // open accessory and store associated AccessoryHandle in map
+                ParcelFileDescriptor pfd = mService.openAccessory(accessory);
+                AccessoryHandle newHandle = new AccessoryHandle(pfd, openingInputStream,
+                        !openingInputStream);
+                mAccessoryHandleMap.put(accessory, newHandle);
+
+                return newHandle;
+            }
+
+            // if accessory is already in map, get modified handle
+            AccessoryHandle currentHandle = mAccessoryHandleMap.get(accessory);
+            if (currentHandle == null) {
+                throw new IllegalStateException("Accessory doesn't have an associated handle yet!");
+            }
+
+            AccessoryHandle modifiedHandle = getModifiedHandleForOpeningStream(
+                    openingInputStream, currentHandle);
+
+            mAccessoryHandleMap.put(accessory, modifiedHandle);
+
+            return modifiedHandle;
+        }
+    }
+
+    private AccessoryHandle getModifiedHandleForOpeningStream(boolean openingInputStream,
+            @NonNull AccessoryHandle currentHandle) {
+        if (currentHandle.isInputStreamOpened() && openingInputStream) {
+            throw new IllegalStateException("Input stream already open for this accessory! "
+                    + "Please close the existing input stream before opening a new one.");
+        }
+
+        if (currentHandle.isOutputStreamOpened() && !openingInputStream) {
+            throw new IllegalStateException("Output stream already open for this accessory! "
+                    + "Please close the existing output stream before opening a new one.");
+        }
+
+        boolean isInputStreamOpened = openingInputStream || currentHandle.isInputStreamOpened();
+        boolean isOutputStreamOpened = !openingInputStream || currentHandle.isOutputStreamOpened();
+
+        return new AccessoryHandle(
+                currentHandle.getPfd(), isInputStreamOpened, isOutputStreamOpened);
+    }
+
+    /**
+     * Marks the handle for the given accessory closed for input or output, and closes the handle
+     * and removes it from the map if there are no more I/O streams associated with the handle.
+     */
+    private void closeHandleForAccessory(UsbAccessory accessory, boolean closingInputStream)
+            throws IOException {
+        synchronized (mAccessoryHandleMapLock) {
+            AccessoryHandle currentHandle = mAccessoryHandleMap.get(accessory);
+
+            if (currentHandle == null) {
+                throw new IllegalStateException(
+                        "No handle has been initialised for this accessory!");
+            }
+
+            AccessoryHandle modifiedHandle = getModifiedHandleForClosingStream(
+                    closingInputStream, currentHandle);
+            if (!modifiedHandle.isOpen()) {
+                //close handle and remove accessory handle pair from map
+                modifiedHandle.getPfd().close();
+                mAccessoryHandleMap.remove(accessory);
+            } else {
+                mAccessoryHandleMap.put(accessory, modifiedHandle);
+            }
+        }
+    }
+
+    private AccessoryHandle getModifiedHandleForClosingStream(boolean closingInputStream,
+            @NonNull AccessoryHandle currentHandle) {
+        if (!currentHandle.isInputStreamOpened() && closingInputStream) {
+            throw new IllegalStateException(
+                    "Attempting to close an input stream that has not been opened "
+                            + "for this accessory!");
+        }
+
+        if (!currentHandle.isOutputStreamOpened() && !closingInputStream) {
+            throw new IllegalStateException(
+                    "Attempting to close an output stream that has not been opened "
+                            + "for this accessory!");
+        }
+
+        boolean isInputStreamOpened = !closingInputStream && currentHandle.isInputStreamOpened();
+        boolean isOutputStreamOpened = closingInputStream && currentHandle.isOutputStreamOpened();
+
+        return new AccessoryHandle(
+                currentHandle.getPfd(), isInputStreamOpened, isOutputStreamOpened);
+    }
+
+    /**
+     * An InputStream you can create on a UsbAccessory, which will
+     * take care of calling {@link ParcelFileDescriptor#close
+     * ParcelFileDescriptor.close()} for you when the stream is closed.
+     */
+    private class AccessoryAutoCloseInputStream extends FileInputStream {
+
+        private final ParcelFileDescriptor mPfd;
+        private final UsbAccessory mAccessory;
+
+        AccessoryAutoCloseInputStream(UsbAccessory accessory, ParcelFileDescriptor pfd) {
+            super(pfd.getFileDescriptor());
+            this.mAccessory = accessory;
+            this.mPfd = pfd;
+        }
+
+        @Override
+        public void close() throws IOException {
+            /* TODO(b/377850642) : Ensure the stream is closed even if client does not
+                explicitly close the stream to avoid corrupt FDs*/
+            super.close();
+            closeHandleForAccessory(mAccessory, true);
+        }
+
+
+        @Override
+        public int read() throws IOException {
+            final int result = super.read();
+            checkError(result);
+            return result;
+        }
+
+        @Override
+        public int read(byte[] b) throws IOException {
+            final int result = super.read(b);
+            checkError(result);
+            return result;
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            final int result = super.read(b, off, len);
+            checkError(result);
+            return result;
+        }
+
+        private void checkError(int result) throws IOException {
+            if (result == -1 && mPfd.canDetectErrors()) {
+                mPfd.checkError();
+            }
+        }
+    }
+
+    /**
+     * An OutputStream you can create on a UsbAccessory, which will
+     * take care of calling {@link ParcelFileDescriptor#close
+     * ParcelFileDescriptor.close()} for you when the stream is closed.
+     */
+    private class AccessoryAutoCloseOutputStream extends FileOutputStream {
+        private final UsbAccessory mAccessory;
+
+        AccessoryAutoCloseOutputStream(UsbAccessory accessory, ParcelFileDescriptor pfd) {
+            super(pfd.getFileDescriptor());
+            mAccessory = accessory;
+        }
+
+        @Override
+        public void close() throws IOException {
+            /* TODO(b/377850642) : Ensure the stream is closed even if client does not
+                explicitly close the stream to avoid corrupt FDs*/
+            super.close();
+            closeHandleForAccessory(mAccessory, false);
+        }
+    }
+
+    /**
+     * Holds file descriptor and marks whether input and output streams have been opened for it.
+     */
+    private static class AccessoryHandle {
+        private final ParcelFileDescriptor mPfd;
+        private final boolean mInputStreamOpened;
+        private final boolean mOutputStreamOpened;
+        AccessoryHandle(ParcelFileDescriptor parcelFileDescriptor,
+                boolean inputStreamOpened, boolean outputStreamOpened) {
+            mPfd = parcelFileDescriptor;
+            mInputStreamOpened = inputStreamOpened;
+            mOutputStreamOpened = outputStreamOpened;
+        }
+
+        public ParcelFileDescriptor getPfd() {
+            return mPfd;
+        }
+
+        public boolean isInputStreamOpened() {
+            return mInputStreamOpened;
+        }
+
+        public boolean isOutputStreamOpened() {
+            return mOutputStreamOpened;
+        }
+
+        public boolean isOpen() {
+            return (mInputStreamOpened || mOutputStreamOpened);
+        }
+    }
+
     private final Context mContext;
     private final IUsbManager mService;
     private final Object mDisplayPortListenersLock = new Object();
@@ -831,6 +1046,11 @@
     @GuardedBy("mDisplayPortListenersLock")
     private DisplayPortAltModeInfoDispatchingListener mDisplayPortServiceListener;
 
+    private final Object mAccessoryHandleMapLock = new Object();
+    @GuardedBy("mAccessoryHandleMapLock")
+    private ArrayMap<UsbAccessory, AccessoryHandle> mAccessoryHandleMap;
+
+
     /**
      * @hide
      */
@@ -922,6 +1142,10 @@
      * data of a USB transfer should be read at once. If only a partial request is read the rest of
      * the transfer is dropped.
      *
+     * <p>It is strongly recommended to use newer methods instead of this method,
+     * since this method may provide sub-optimal performance on some devices.
+     * This method could potentially face interim performance degradation as well.
+     *
      * @param accessory the USB accessory to open
      * @return file descriptor, or null if the accessory could not be opened.
      */
@@ -935,6 +1159,49 @@
     }
 
     /**
+     * Opens an input stream for reading from the USB accessory.
+     * If accessory is not open at this point, accessory will first be opened.
+     * <p>If data is read from the created {@link java.io.InputStream} all
+     * data of a USB transfer should be read at once. If only a partial request is read, the rest of
+     * the transfer is dropped.
+     * <p>The caller is responsible for ensuring that the returned stream is closed.
+     *
+     * @param accessory the USB accessory to open an input stream for
+     * @return input stream to read from given USB accessory
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+    @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
+    public @NonNull InputStream openAccessoryInputStream(@NonNull UsbAccessory accessory) {
+        try {
+            return new AccessoryAutoCloseInputStream(accessory,
+                    openHandleForAccessory(accessory, true).getPfd());
+
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Opens an output stream for writing to the USB accessory.
+     * If accessory is not open at this point, accessory will first be opened.
+     * <p>The caller is responsible for ensuring that the returned stream is closed.
+     *
+     * @param accessory the USB accessory to open an output stream for
+     * @return output stream to write to given USB accessory
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+    @RequiresFeature(PackageManager.FEATURE_USB_ACCESSORY)
+    public @NonNull OutputStream openAccessoryOutputStream(@NonNull UsbAccessory accessory) {
+        try {
+            return new AccessoryAutoCloseOutputStream(accessory,
+                    openHandleForAccessory(accessory, false).getPfd());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+    }
+
+    /**
      * Gets the functionfs control file descriptor for the given function, with
      * the usb descriptors and strings already written. The file descriptor is used
      * by the function implementation to handle events and control requests.
@@ -1293,7 +1560,7 @@
      * <p>
      * This function returns the current USB bandwidth through USB Gadget HAL.
      * It should be used when Android device is in USB peripheral mode and
-     * connects to a USB host. If USB state is not configued, API will return
+     * connects to a USB host. If USB state is not configured, API will return
      * {@value #USB_DATA_TRANSFER_RATE_UNKNOWN}. In addition, the unit of the
      * return value is Mbps.
      * </p>
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index 3b7a9e9..b719a7c 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -31,3 +31,11 @@
     description: "Feature flag to enable exposing usb speed system api"
     bug: "373653182"
 }
+
+flag {
+    name: "enable_accessory_stream_api"
+    is_exported: true
+    namespace: "usb"
+    description: "Feature flag to enable stream APIs for Accessory mode"
+    bug: "369356693"
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8c3f0ef..ae83668 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -55,6 +55,7 @@
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
 import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
+import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API;
 import static android.view.inputmethod.Flags.ctrlShiftShortcut;
 import static android.view.inputmethod.Flags.predictiveBackIme;
 
@@ -4392,6 +4393,39 @@
     }
 
     /**
+     * Called when the requested visibility of a custom IME Switcher button changes.
+     *
+     * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher
+     * button inside this bar. However, the IME can request hiding the bar provided by the system
+     * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides
+     * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful,
+     * then it becomes the IME's responsibility to provide a custom IME Switcher button in its
+     * input view, with equivalent functionality.</p>
+     *
+     * <p>This custom button is only requested to be visible when the system provides the IME
+     * navigation bar, both the bar and the IME Switcher button inside it should be visible,
+     * but the IME successfully requested to hide the bar. This does not depend on the current
+     * visibility of the IME. It could be called with {@code true} while the IME is hidden, in
+     * which case the IME should prepare to show the button as soon as the IME itself is shown.</p>
+     *
+     * <p>This is only called when the requested visibility changes. The default value is
+     * {@code false} and as such, this will not be called initially if the resulting value is
+     * {@code false}.</p>
+     *
+     * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently
+     * visible. However, this is not guaranteed to be called before the IME is shown, as it depends
+     * on when the IME requested hiding the IME navigation bar. If the request is sent during
+     * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after
+     * {@link #onWindowShown}, but before the first IME frame is drawn.</p>
+     *
+     * @param visible whether the button is requested visible or not.
+     */
+    @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API)
+    public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) {
+        // Intentionally empty
+    }
+
+    /**
      * Called when the IME switch button was clicked from the client. Depending on the number of
      * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input
      * method picker dialog.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index b08454d..38be8d9 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -41,6 +41,7 @@
 import android.view.WindowInsetsController.Appearance;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 
@@ -178,6 +179,9 @@
 
         private boolean mDrawLegacyNavigationBarBackground;
 
+        /** Whether a custom IME Switcher button should be visible. */
+        private boolean mCustomImeSwitcherVisible;
+
         private final Rect mTempRect = new Rect();
         private final int[] mTempPos = new int[2];
 
@@ -265,6 +269,7 @@
                     // IME navigation bar.
                     boolean visible = insets.isVisible(captionBar());
                     mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
+                    checkCustomImeSwitcherVisibility();
                 }
                 return view.onApplyWindowInsets(insets);
             });
@@ -491,6 +496,8 @@
                     mShouldShowImeSwitcherWhenImeIsShown;
             mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
 
+            checkCustomImeSwitcherVisibility();
+
             mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
                     .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar));
 
@@ -616,12 +623,33 @@
                     && mNavigationBarFrame.getVisibility() == View.VISIBLE;
         }
 
+        /**
+         * Checks if a custom IME Switcher button should be visible, and notifies the IME when this
+         * state changes. This can only be {@code true} if three conditions are met:
+         *
+         * <li>The IME should draw the IME navigation bar.</li>
+         * <li>The IME Switcher button should be visible when the IME is visible.</li>
+         * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li>
+         */
+        private void checkCustomImeSwitcherVisibility() {
+            if (!Flags.imeSwitcherRevampApi()) {
+                return;
+            }
+            final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown
+                    && mNavigationBarFrame != null && !isShown();
+            if (visible != mCustomImeSwitcherVisible) {
+                mCustomImeSwitcherVisible = visible;
+                mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible);
+            }
+        }
+
         @Override
         public String toDebugString() {
             return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
                     + " mNavigationBarFrame=" + mNavigationBarFrame
                     + " mShouldShowImeSwitcherWhenImeIsShown="
                     + mShouldShowImeSwitcherWhenImeIsShown
+                    + " mCustomImeSwitcherVisible="  + mCustomImeSwitcherVisible
                     + " mAppearance=0x" + Integer.toHexString(mAppearance)
                     + " mDarkIntensity=" + mDarkIntensity
                     + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 6d4e284..517418a 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1011,13 +1011,7 @@
             return mTags.length > 0 ? mTags[0] : null;
         }
 
-        // TODO: The following three trivial getters are purely for testing and will be removed
-        // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
-        // diffing it etc.
-
-
         /** @hide */
-        @VisibleForTesting
         public int[] getUids() {
             int[] uids = new int[mSize];
             System.arraycopy(mUids, 0, uids, 0, mSize);
@@ -1025,7 +1019,6 @@
         }
 
         /** @hide */
-        @VisibleForTesting
         public String[] getTags() {
             String[] tags = new String[mSize];
             System.arraycopy(mTags, 0, tags, 0, mSize);
@@ -1033,7 +1026,6 @@
         }
 
         /** @hide */
-        @VisibleForTesting
         public int getSize() {
             return mSize;
         }
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 60a0ae3..92c5c20 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -386,3 +386,17 @@
     description: "This fixed read-only flag is used to enable new ranging permission for all ranging use cases."
     bug: "370977414"
 }
+
+flag {
+    name: "system_selection_toolbar_enabled"
+    namespace: "permissions"
+    description: "Enables the system selection toolbar feature."
+    bug: "363318732"
+}
+
+flag {
+    name: "use_system_selection_toolbar_in_sysui"
+    namespace: "permissions"
+    description: "Uses the SysUi process to host the SelectionToolbarRenderService."
+    bug: "363318732"
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 7cb0ffc..ce90121 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -109,7 +109,7 @@
 
 flag {
     name: "afl_api"
-    namespace: "platform_security"
+    namespace: "hardware_backed_security"
     description: "AFL feature"
     bug: "365994454"
 }
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 02923ed..f43f172 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -163,10 +163,12 @@
 }
 
 flag {
-  name: "typeface_redesign"
+  name: "typeface_redesign_readonly"
   namespace: "text"
   description: "Decouple variation settings, weight and style information from Typeface class"
   bug: "361260253"
+  # This feature does not support runtime flag switch which leads crash in System UI.
+  is_fixed_read_only: true
 }
 
 flag {
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 6b5a367..7a01ad3 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -54,6 +54,7 @@
             Flags::enableDesktopWindowingWallpaperActivity, true),
     ENABLE_DESKTOP_WINDOWING_MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
     ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
+    ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
     ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
     ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
     ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index d39ecab..f474b34 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -353,6 +353,16 @@
 }
 
 flag {
+    name: "enable_desktop_system_dialogs_transitions"
+    namespace: "lse_desktop_experience"
+    description: "Enables custom transitions for system dialogs in Desktop Mode."
+    bug: "335638193"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_move_to_next_display_shortcut"
     namespace: "lse_desktop_experience"
     description: "Add new keyboard shortcut of moving a task into next display"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 68e78fe..d9de38a 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -268,6 +268,16 @@
 }
 
 flag {
+  name: "system_ui_post_animation_end"
+  namespace: "windowing_frontend"
+  description: "Run AnimatorListener#onAnimationEnd on next frame for SystemUI"
+  bug: "300035126"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "system_ui_immersive_confirmation_dialog"
   namespace: "windowing_frontend"
   description: "Enable the implementation of the immersive confirmation dialog on system UI side by default"
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 50252c1..4240614 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -538,7 +538,7 @@
         return false;
     }
 
-    if (!(useContextAttributionSource && flags::use_context_attribution_source())) {
+    if (!(useContextAttributionSource && flags::data_delivery_permission_checks())) {
         clientAttribution.uid = Camera::USE_CALLING_UID;
         clientAttribution.pid = Camera::USE_CALLING_PID;
     }
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index a382d79..f39508d 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -151,6 +151,7 @@
         ":HelloWorldUsingSdk1And2",
         ":HelloWorldUsingSdkMalformedNegativeVersion",
         ":CtsStaticSharedLibConsumerApp1",
+        ":CtsStaticSharedLibConsumerApp3",
     ],
 }
 
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 3f7c83a..5d8ff87 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -41,6 +41,8 @@
             value="/data/local/tmp/tests/coretests/pm/HelloWorldSdk1.apk"/>
         <option name="push-file" key="CtsStaticSharedLibConsumerApp1.apk"
             value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp1.apk"/>
+        <option name="push-file" key="CtsStaticSharedLibConsumerApp3.apk"
+            value="/data/local/tmp/tests/coretests/pm/CtsStaticSharedLibConsumerApp3.apk"/>
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
index d4618d7..0db49a7 100644
--- a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
+++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
@@ -72,6 +72,12 @@
     private static final String TEST_APP_USING_SDK_MALFORMED_VERSION =
             "HelloWorldUsingSdkMalformedNegativeVersion.apk";
     private static final String TEST_APP_USING_STATIC_LIB = "CtsStaticSharedLibConsumerApp1.apk";
+    private static final String TEST_APP_USING_STATIC_LIB_TWO_CERTS =
+            "CtsStaticSharedLibConsumerApp3.apk";
+    private static final String STATIC_LIB_CERT_1 =
+            "70fbd440503ec0bf41f3f21fcc83ffd39880133c27deb0945ed677c6f31d72fb";
+    private static final String STATIC_LIB_CERT_2 =
+            "e49582ff3a0aa4c5589fc5feaac6b7d6e757199dd0c6742df7bf37c2ffef95f5";
     private static final String TEST_SDK1 = "HelloWorldSdk1.apk";
     private static final String TEST_SDK1_PACKAGE = "com.test.sdk1_1";
     private static final String TEST_SDK1_NAME = "com.test.sdk1";
@@ -86,7 +92,7 @@
 
     @Before
     public void setUp() throws IOException {
-        mTmpDir = mTemporaryFolder.newFolder("DexMetadataHelperTest");
+        mTmpDir = mTemporaryFolder.newFolder("ApkLiteParseUtilsTest");
     }
 
     @After
@@ -108,9 +114,8 @@
         assertThat(baseApk.getUsesSdkLibrariesVersionsMajor()).asList().containsExactly(
                 TEST_SDK1_VERSION, TEST_SDK2_VERSION
         );
-        for (String[] certDigests: baseApk.getUsesSdkLibrariesCertDigests()) {
-            assertThat(certDigests).asList().containsExactly("");
-        }
+        String[][] expectedCerts = {{""}, {""}};
+        assertThat(baseApk.getUsesSdkLibrariesCertDigests()).isEqualTo(expectedCerts);
     }
 
     @SuppressLint("CheckResult")
@@ -126,18 +131,13 @@
         ApkLite baseApk = result.getResult();
 
         String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
-        assertThat(liteCerts).isNotNull();
-        for (String[] certDigests: liteCerts) {
-            assertThat(certDigests).asList().containsExactly(certDigest);
-        }
+        String[][] expectedCerts = {{certDigest}, {certDigest}};
+        assertThat(liteCerts).isEqualTo(expectedCerts);
 
         // Same for package parser
         AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
         String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
-        assertThat(pkgCerts).isNotNull();
-        for (int i = 0; i < liteCerts.length; i++) {
-            assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
-        }
+        assertThat(liteCerts).isEqualTo(pkgCerts);
     }
 
 
@@ -160,9 +160,7 @@
 
         String[][] liteCerts = baseApk.getUsesSdkLibrariesCertDigests();
         String[][] pkgCerts = pkg.getUsesSdkLibrariesCertDigests();
-        for (int i = 0; i < liteCerts.length; i++) {
-            assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
-        }
+        assertThat(liteCerts).isEqualTo(pkgCerts);
     }
 
     @SuppressLint("CheckResult")
@@ -184,9 +182,27 @@
 
         String[][] liteCerts = baseApk.getUsesStaticLibrariesCertDigests();
         String[][] pkgCerts = pkg.getUsesStaticLibrariesCertDigests();
-        for (int i = 0; i < liteCerts.length; i++) {
-            assertThat(liteCerts[i]).isEqualTo(pkgCerts[i]);
-        }
+        assertThat(liteCerts).isEqualTo(pkgCerts);
+    }
+
+    @Test
+    public void testParseApkLite_getUsesStaticLibrary_twoCerts()
+            throws Exception {
+        File apkFile = copyApkToTmpDir(TEST_APP_USING_STATIC_LIB_TWO_CERTS);
+        ParseResult<ApkLite> result = ApkLiteParseUtils
+                .parseApkLite(ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        assertThat(result.isError()).isFalse();
+        ApkLite baseApk = result.getResult();
+
+        // There are two certs.
+        String[][] expectedCerts = {{STATIC_LIB_CERT_1, STATIC_LIB_CERT_2}};
+        String[][] liteCerts = baseApk.getUsesStaticLibrariesCertDigests();
+        assertThat(liteCerts).isEqualTo(expectedCerts);
+
+        // And they are same as package parser.
+        AndroidPackage pkg = mPackageParser2.parsePackage(apkFile, 0, true).hideAsFinal();
+        String[][] pkgCerts = pkg.getUsesStaticLibrariesCertDigests();
+        assertThat(liteCerts).isEqualTo(pkgCerts);
     }
 
     @SuppressLint("CheckResult")
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 8bb32568..56bb0f0 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2119,7 +2119,7 @@
      * @see FontVariationAxis
      */
     public boolean setFontVariationSettings(String fontVariationSettings) {
-        final boolean useFontVariationStore = Flags.typefaceRedesign()
+        final boolean useFontVariationStore = Flags.typefaceRedesignReadonly()
                 && CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT);
         if (useFontVariationStore) {
             FontVariationAxis[] axes =
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index ed17fde..43216ba 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -133,7 +133,7 @@
     @NonNull
     public Font getFont(@IntRange(from = 0) int index) {
         Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
-        if (Flags.typefaceRedesign()) {
+        if (Flags.typefaceRedesignReadonly()) {
             return mFonts.get(nGetFontId(mLayoutPtr, index));
         }
         return mFonts.get(index);
@@ -252,7 +252,7 @@
         mXOffset = xOffset;
         mYOffset = yOffset;
 
-        if (Flags.typefaceRedesign()) {
+        if (Flags.typefaceRedesignReadonly()) {
             int fontCount = nGetFontCount(layoutPtr);
             mFonts = new ArrayList<>(fontCount);
             for (int i = 0; i < fontCount; ++i) {
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 4d7be39..76eb207 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -19,6 +19,7 @@
 import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
@@ -104,6 +105,30 @@
     @GuardedBy("mLock")
     private int mLastReportedRearDisplayPresentationStatus;
 
+    @VisibleForTesting
+    static int getRdmV1Identifier(List<DeviceState> currentSupportedDeviceStates) {
+        for (int i = 0; i < currentSupportedDeviceStates.size(); i++) {
+            DeviceState state = currentSupportedDeviceStates.get(i);
+            if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY)
+                    && !state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) {
+                return state.getIdentifier();
+            }
+        }
+        return INVALID_DEVICE_STATE_IDENTIFIER;
+    }
+
+    @VisibleForTesting
+    static int getRdmV2Identifier(List<DeviceState> currentSupportedDeviceStates) {
+        for (int i = 0; i < currentSupportedDeviceStates.size(); i++) {
+            DeviceState state = currentSupportedDeviceStates.get(i);
+            if (state.hasProperties(PROPERTY_FEATURE_REAR_DISPLAY,
+                    PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT)) {
+                return state.getIdentifier();
+            }
+        }
+        return INVALID_DEVICE_STATE_IDENTIFIER;
+    }
+
     public WindowAreaComponentImpl(@NonNull Context context) {
         mDeviceStateManager = context.getSystemService(DeviceStateManager.class);
         mDisplayManager = context.getSystemService(DisplayManager.class);
@@ -112,12 +137,10 @@
         mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedDeviceStates();
 
         if (Flags.deviceStatePropertyMigration()) {
-            for (int i = 0; i < mCurrentSupportedDeviceStates.size(); i++) {
-                DeviceState state = mCurrentSupportedDeviceStates.get(i);
-                if (state.hasProperty(PROPERTY_FEATURE_REAR_DISPLAY)) {
-                    mRearDisplayState = state.getIdentifier();
-                    break;
-                }
+            if (Flags.deviceStateRdmV2()) {
+                mRearDisplayState = getRdmV2Identifier(mCurrentSupportedDeviceStates);
+            } else {
+                mRearDisplayState = getRdmV1Identifier(mCurrentSupportedDeviceStates);
             }
         } else {
             mFoldedDeviceStates = context.getResources().getIntArray(
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
index ccb4ebe..d677fef 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/area/WindowAreaComponentImplTests.java
@@ -16,8 +16,13 @@
 
 package androidx.window.extensions.area;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+
 import static org.junit.Assert.assertEquals;
 
+import android.hardware.devicestate.DeviceState;
 import android.platform.test.annotations.Presubmit;
 import android.util.DisplayMetrics;
 import android.view.Surface;
@@ -29,11 +34,34 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WindowAreaComponentImplTests {
 
+    private static final DeviceState REAR_DISPLAY_STATE_V1 = new DeviceState(
+            new DeviceState.Configuration.Builder(1, "STATE_0")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FEATURE_REAR_DISPLAY))
+                    .build());
+    private static final DeviceState REAR_DISPLAY_STATE_V2 = new DeviceState(
+            new DeviceState.Configuration.Builder(2, "STATE_0")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FEATURE_REAR_DISPLAY,
+                                    PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT))
+                    .build());
+    // The PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT state must be present together with the
+    // PROPERTY_FEATURE_REAR_DISPLAY state in order to be a valid state.
+    private static final DeviceState INVALID_REAR_DISPLAY_STATE = new DeviceState(
+            new DeviceState.Configuration.Builder(2, "STATE_0")
+                    .setSystemProperties(
+                            Set.of(PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT))
+                    .build());
+
     private final DisplayMetrics mTestDisplayMetrics = new DisplayMetrics();
 
     @Before
@@ -93,4 +121,37 @@
                 Surface.ROTATION_270, Surface.ROTATION_0, mTestDisplayMetrics);
         assertEquals(expectedMetrics, mTestDisplayMetrics);
     }
+
+    @Test
+    public void testRdmV1Identifier() {
+        final List<DeviceState> supportedStates = new ArrayList<>();
+        supportedStates.add(REAR_DISPLAY_STATE_V2);
+        assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+                WindowAreaComponentImpl.getRdmV1Identifier(supportedStates));
+
+        supportedStates.add(REAR_DISPLAY_STATE_V1);
+        assertEquals(REAR_DISPLAY_STATE_V1.getIdentifier(),
+                WindowAreaComponentImpl.getRdmV1Identifier(supportedStates));
+    }
+
+    @Test
+    public void testRdmV2Identifier_whenStateIsImproperlyConfigured() {
+        final List<DeviceState> supportedStates = new ArrayList<>();
+        supportedStates.add(INVALID_REAR_DISPLAY_STATE);
+        assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+                WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+    }
+
+    @Test
+    public void testRdmV2Identifier_whenStateIsProperlyConfigured() {
+        final List<DeviceState> supportedStates = new ArrayList<>();
+
+        supportedStates.add(REAR_DISPLAY_STATE_V1);
+        assertEquals(INVALID_DEVICE_STATE_IDENTIFIER,
+                WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+
+        supportedStates.add(REAR_DISPLAY_STATE_V2);
+        assertEquals(REAR_DISPLAY_STATE_V2.getIdentifier(),
+                WindowAreaComponentImpl.getRdmV2Identifier(supportedStates));
+    }
 }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 0b515f5..5f42bb1 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -475,6 +475,6 @@
 
         override fun hideCurrentInputMethod() {}
 
-        override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+        override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
     }
 }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 0d742cc..6ac36a3 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -375,7 +375,7 @@
             override fun hideCurrentInputMethod() {
             }
 
-            override fun updateBubbleBarLocation(location: BubbleBarLocation) {
+            override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {
             }
         }
     }
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 00d9a93..0044593 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -351,7 +351,7 @@
 
             override fun hideCurrentInputMethod() {}
 
-            override fun updateBubbleBarLocation(location: BubbleBarLocation) {}
+            override fun updateBubbleBarLocation(location: BubbleBarLocation, source: Int) {}
         }
     }
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
index 191875d..84a22b8 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.shared.bubbles
 
+import android.annotation.IntDef
 import android.os.Parcel
 import android.os.Parcelable
 
@@ -60,4 +61,36 @@
             override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size)
         }
     }
+
+    /** Define set of constants that allow to determine why location changed. */
+    @IntDef(
+        UpdateSource.DRAG_BAR,
+        UpdateSource.DRAG_BUBBLE,
+        UpdateSource.DRAG_EXP_VIEW,
+        UpdateSource.A11Y_ACTION_BAR,
+        UpdateSource.A11Y_ACTION_BUBBLE,
+        UpdateSource.A11Y_ACTION_EXP_VIEW,
+    )
+    @Retention(AnnotationRetention.SOURCE)
+    annotation class UpdateSource {
+        companion object {
+            /** Location changed from dragging the bar */
+            const val DRAG_BAR = 1
+
+            /** Location changed from dragging the bubble */
+            const val DRAG_BUBBLE = 2
+
+            /** Location changed from dragging the expanded view */
+            const val DRAG_EXP_VIEW = 3
+
+            /** Location changed via a11y action on the bar */
+            const val A11Y_ACTION_BAR = 4
+
+            /** Location changed via a11y action on the bubble */
+            const val A11Y_ACTION_BUBBLE = 5
+
+            /** Location changed via a11y action on the expanded view */
+            const val A11Y_ACTION_EXP_VIEW = 6
+        }
+    }
 }
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 c92a278..ce7a977 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
@@ -1122,7 +1122,7 @@
         final BackMotionEvent backFinish = mCurrentTracker
                 .createProgressEvent();
         dispatchOnBackProgressed(mActiveCallback, backFinish);
-        if (!mBackGestureStarted) {
+        if (mCurrentTracker.isFinished()) {
             // if the down -> up gesture happened before animation
             // start, we have to trigger the uninterruptible transition
             // to finish the back animation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 14f8cc7..0fd98ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -740,8 +740,10 @@
     /**
      * Update bubble bar location and trigger and update to listeners
      */
-    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
+            @BubbleBarLocation.UpdateSource int source) {
         if (canShowAsBubbleBar()) {
+            BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation();
             mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
             if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
                 mLayerView.updateExpandedView();
@@ -749,13 +751,47 @@
             BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
             bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
             mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+
+            logBubbleBarLocationIfChanged(bubbleBarLocation, previousLocation, source);
+        }
+    }
+
+    private void logBubbleBarLocationIfChanged(BubbleBarLocation location,
+            BubbleBarLocation previous,
+            @BubbleBarLocation.UpdateSource int source) {
+        if (mLayerView == null) {
+            return;
+        }
+        boolean isRtl = mLayerView.isLayoutRtl();
+        boolean wasLeft = previous.isOnLeft(isRtl);
+        boolean onLeft = location.isOnLeft(isRtl);
+        if (wasLeft == onLeft) {
+            // No changes, skip logging
+            return;
+        }
+        switch (source) {
+            case BubbleBarLocation.UpdateSource.DRAG_BAR:
+            case BubbleBarLocation.UpdateSource.A11Y_ACTION_BAR:
+                mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR
+                        : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
+                break;
+            case BubbleBarLocation.UpdateSource.DRAG_BUBBLE:
+            case BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE:
+                mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE
+                        : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
+                break;
+            case BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW:
+            case BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW:
+                // TODO(b/349845968): move logging from BubbleBarLayerView to here
+                break;
         }
     }
 
     /**
      * Animate bubble bar to the given location. The location change is transient. It does not
      * update the state of the bubble bar.
-     * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+     * To update bubble bar pinned location, use
+     * {@link #setBubbleBarLocation(BubbleBarLocation, int)}.
      */
     public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
         if (canShowAsBubbleBar()) {
@@ -2568,9 +2604,10 @@
         }
 
         @Override
-        public void setBubbleBarLocation(BubbleBarLocation location) {
+        public void setBubbleBarLocation(BubbleBarLocation location,
+                @BubbleBarLocation.UpdateSource int source) {
             mMainExecutor.execute(() ->
-                    mController.setBubbleBarLocation(location));
+                    mController.setBubbleBarLocation(location, source));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
index ec4854b..6423eed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -32,7 +32,10 @@
     fun isStackExpanded(): Boolean
     fun isShowingAsBubbleBar(): Boolean
     fun hideCurrentInputMethod()
-    fun updateBubbleBarLocation(location: BubbleBarLocation)
+    fun updateBubbleBarLocation(
+        location: BubbleBarLocation,
+        @BubbleBarLocation.UpdateSource source: Int,
+    )
 
     companion object {
         /**
@@ -82,8 +85,11 @@
                     controller.hideCurrentInputMethod()
                 }
 
-                override fun updateBubbleBarLocation(location: BubbleBarLocation) {
-                    controller.bubbleBarLocation = location
+                override fun updateBubbleBarLocation(
+                    location: BubbleBarLocation,
+                    @BubbleBarLocation.UpdateSource source: Int,
+                ) {
+                    controller.setBubbleBarLocation(location, source)
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 1855b93..9c2d3543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -44,7 +44,7 @@
 
     oneway void showUserEducation(in int positionX, in int positionY) = 8;
 
-    oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9;
+    oneway void setBubbleBarLocation(in BubbleBarLocation location, in int source) = 9;
 
     oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 272dfec..3764bcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -637,11 +637,13 @@
                 return true;
             }
             if (action == R.id.action_move_bubble_bar_left) {
-                mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT);
+                mManager.updateBubbleBarLocation(BubbleBarLocation.LEFT,
+                        BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
                 return true;
             }
             if (action == R.id.action_move_bubble_bar_right) {
-                mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT);
+                mManager.updateBubbleBarLocation(BubbleBarLocation.RIGHT,
+                        BubbleBarLocation.UpdateSource.A11Y_ACTION_EXP_VIEW);
                 return true;
             }
             return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 1f77abe..0c05e3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -441,7 +441,8 @@
 
         @Override
         public void onRelease(@NonNull BubbleBarLocation location) {
-            mBubbleController.setBubbleBarLocation(location);
+            mBubbleController.setBubbleBarLocation(location,
+                    BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
             if (location != mInitialLocation) {
                 BubbleLogger.Event event = location.isOnLeft(isLayoutRtl())
                         ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW
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 44fce81..601cf70 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
@@ -67,6 +67,7 @@
 import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopBackNavigationTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
 import com.android.wm.shell.desktopmode.DesktopImmersiveController;
 import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
@@ -915,6 +916,16 @@
 
     @WMSingleton
     @Provides
+    static DesktopBackNavigationTransitionHandler provideDesktopBackNavigationTransitionHandler(
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellAnimationThread ShellExecutor animExecutor,
+            DisplayController displayController) {
+        return new DesktopBackNavigationTransitionHandler(mainExecutor, animExecutor,
+                displayController);
+    }
+
+    @WMSingleton
+    @Provides
     static DesktopModeDragAndDropTransitionHandler provideDesktopModeDragAndDropTransitionHandler(
             Transitions transitions) {
         return new DesktopModeDragAndDropTransitionHandler(transitions);
@@ -964,6 +975,7 @@
             Optional<DesktopRepository> desktopRepository,
             Transitions transitions,
             ShellTaskOrganizer shellTaskOrganizer,
+            Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
             ShellInit shellInit) {
         return desktopRepository.flatMap(
                 repository ->
@@ -973,6 +985,7 @@
                                         repository,
                                         transitions,
                                         shellTaskOrganizer,
+                                        desktopMixedTransitionHandler.get(),
                                         shellInit)));
     }
 
@@ -985,6 +998,7 @@
             FreeformTaskTransitionHandler freeformTaskTransitionHandler,
             CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
             Optional<DesktopImmersiveController> desktopImmersiveController,
+            DesktopBackNavigationTransitionHandler desktopBackNavigationTransitionHandler,
             InteractionJankMonitor interactionJankMonitor,
             @ShellMainThread Handler handler,
             ShellInit shellInit,
@@ -1001,6 +1015,7 @@
                         freeformTaskTransitionHandler,
                         closeDesktopTaskTransitionHandler,
                         desktopImmersiveController.get(),
+                        desktopBackNavigationTransitionHandler,
                         interactionJankMonitor,
                         handler,
                         shellInit,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
new file mode 100644
index 0000000..83b0f84
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandler.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.DisplayMetrics
+import android.view.SurfaceControl.Transaction
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.animation.MinimizeAnimator.create
+import com.android.wm.shell.transition.Transitions
+
+/**
+ * The [Transitions.TransitionHandler] that handles transitions for tasks that are closing or going
+ * to back as part of back navigation. This handler is used only for animating transitions.
+ */
+class DesktopBackNavigationTransitionHandler(
+    private val mainExecutor: ShellExecutor,
+    private val animExecutor: ShellExecutor,
+    private val displayController: DisplayController,
+) : Transitions.TransitionHandler {
+
+    /** Shouldn't handle anything */
+    override fun handleRequest(
+        transition: IBinder,
+        request: TransitionRequestInfo,
+    ): WindowContainerTransaction? = null
+
+    /** Animates a transition with minimizing tasks */
+    override fun startAnimation(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: Transaction,
+        finishTransaction: Transaction,
+        finishCallback: Transitions.TransitionFinishCallback,
+    ): Boolean {
+        if (!TransitionUtil.isClosingType(info.type)) return false
+
+        val animations = mutableListOf<Animator>()
+        val onAnimFinish: (Animator) -> Unit = { animator ->
+            mainExecutor.execute {
+                // Animation completed
+                animations.remove(animator)
+                if (animations.isEmpty()) {
+                    // All animations completed, finish the transition
+                    finishCallback.onTransitionFinished(/* wct= */ null)
+                }
+            }
+        }
+
+        animations +=
+            info.changes
+                .filter {
+                    it.mode == info.type &&
+                            it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+                }
+                .mapNotNull { createMinimizeAnimation(it, finishTransaction, onAnimFinish) }
+        if (animations.isEmpty()) return false
+        animExecutor.execute { animations.forEach(Animator::start) }
+        return true
+    }
+
+    private fun createMinimizeAnimation(
+        change: TransitionInfo.Change,
+        finishTransaction: Transaction,
+        onAnimFinish: (Animator) -> Unit
+    ): Animator? {
+        val t = Transaction()
+        val sc = change.leash
+        finishTransaction.hide(sc)
+        val displayMetrics: DisplayMetrics? =
+            change.taskInfo?.let {
+                displayController.getDisplayContext(it.displayId)?.getResources()?.displayMetrics
+            }
+        return displayMetrics?.let { create(it, change, t, onAnimFinish) }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 01c680d..2001f97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -52,6 +52,7 @@
     private val freeformTaskTransitionHandler: FreeformTaskTransitionHandler,
     private val closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler,
     private val desktopImmersiveController: DesktopImmersiveController,
+    private val desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler,
     private val interactionJankMonitor: InteractionJankMonitor,
     @ShellMainThread private val handler: Handler,
     shellInit: ShellInit,
@@ -161,6 +162,14 @@
                 finishTransaction,
                 finishCallback
             )
+            is PendingMixedTransition.Minimize -> animateMinimizeTransition(
+                pending,
+                transition,
+                info,
+                startTransaction,
+                finishTransaction,
+                finishCallback
+            )
         }
     }
 
@@ -272,6 +281,42 @@
         )
     }
 
+    private fun animateMinimizeTransition(
+        pending: PendingMixedTransition.Minimize,
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction,
+        finishCallback: TransitionFinishCallback,
+    ): Boolean {
+        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue) return false
+
+        val minimizeChange = findDesktopTaskChange(info, pending.minimizingTask)
+        if (minimizeChange == null) {
+            logW("Should have minimizing desktop task")
+            return false
+        }
+        if (pending.isLastTask) {
+            // Dispatch close desktop task animation to the default transition handlers.
+            return dispatchToLeftoverHandler(
+                transition,
+                info,
+                startTransaction,
+                finishTransaction,
+                finishCallback
+            )
+        }
+
+        // Animate minimizing desktop task transition with [DesktopBackNavigationTransitionHandler].
+        return desktopBackNavigationTransitionHandler.startAnimation(
+            transition,
+            info,
+            startTransaction,
+            finishTransaction,
+            finishCallback,
+        )
+    }
+
     override fun onTransitionConsumed(
         transition: IBinder,
         aborted: Boolean,
@@ -400,6 +445,14 @@
             val minimizingTask: Int?,
             val exitingImmersiveTask: Int?,
         ) : PendingMixedTransition()
+
+        /** A task is minimizing. This should be used for task going to back and some closing cases
+         * with back navigation. */
+        data class Minimize(
+            override val transition: IBinder,
+            val minimizingTask: Int,
+            val isLastTask: Boolean,
+        ) : PendingMixedTransition()
     }
 
     private fun logV(msg: String, vararg arguments: Any?) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index bed484c..39586e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -594,6 +594,10 @@
                 FrameworkStatsLog
                     .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__MAXIMIZE_MENU_RESIZE_TRIGGER
             ),
+            DRAG_TO_TOP_RESIZE_TRIGGER(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__DRAG_TO_TOP_RESIZE_TRIGGER
+            ),
         }
 
         /**
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 927fd88..223038f 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
@@ -871,11 +871,10 @@
             return
         }
 
-        // TODO(b/375356605): Introduce a new ResizeTrigger for drag-to-top.
         desktopModeEventLogger.logTaskResizingStarted(
-            ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent, taskInfo, displayController
+            ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, motionEvent, taskInfo, displayController
         )
-        toggleDesktopTaskSize(taskInfo, ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, motionEvent)
+        toggleDesktopTaskSize(taskInfo, ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER, motionEvent)
     }
 
     private fun getMaximizeBounds(taskInfo: RunningTaskInfo, stableBounds: Rect): Rect {
@@ -1291,7 +1290,11 @@
                     // Check if freeform task launch during recents should be handled
                     shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
                     // Check if the closing task needs to be handled
-                    TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
+                    TransitionUtil.isClosingType(request.type) -> handleTaskClosing(
+                        task,
+                        transition,
+                        request.type
+                    )
                     // Check if the top task shouldn't be allowed to enter desktop mode
                     isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
                     // Check if fullscreen task should be updated
@@ -1621,7 +1624,7 @@
     }
 
     /** Handle task closing by removing wallpaper activity if it's the last active task */
-    private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
+    private fun handleTaskClosing(task: RunningTaskInfo, transition: IBinder, requestType: Int): WindowContainerTransaction? {
         logV("handleTaskClosing")
         if (!isDesktopModeShowing(task.displayId))
             return null
@@ -1637,8 +1640,15 @@
         if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
             taskRepository.addClosingTask(task.displayId, task.taskId)
             desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
+        } else if (requestType == TRANSIT_CLOSE) {
+            // Handle closing tasks, tasks that are going to back are handled in
+            // [DesktopTasksTransitionObserver].
+            desktopMixedTransitionHandler.addPendingMixedTransition(
+                DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+                    transition, task.taskId, taskRepository.getVisibleTaskCount(task.displayId) == 1
+                )
+            )
         }
-
         taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
             doesAnyTaskRequireTaskbarRounding(
                 task.displayId,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index d1534da..c39c715 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -46,6 +46,7 @@
     private val desktopRepository: DesktopRepository,
     private val transitions: Transitions,
     private val shellTaskOrganizer: ShellTaskOrganizer,
+    private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
     shellInit: ShellInit
 ) : Transitions.TransitionObserver {
 
@@ -71,7 +72,7 @@
         // TODO: b/332682201 Update repository state
         updateWallpaperToken(info)
         if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
-            handleBackNavigation(info)
+            handleBackNavigation(transition, info)
             removeTaskIfNeeded(info)
         }
         removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
@@ -95,7 +96,7 @@
         }
     }
 
-    private fun handleBackNavigation(info: TransitionInfo) {
+    private fun handleBackNavigation(transition: IBinder, info: TransitionInfo) {
         // When default back navigation happens, transition type is TO_BACK and the change is
         // TO_BACK. Mark the task going to back as minimized.
         if (info.type == TRANSIT_TO_BACK) {
@@ -105,10 +106,14 @@
                     continue
                 }
 
-                if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
+                val visibleTaskCount = desktopRepository.getVisibleTaskCount(taskInfo.displayId)
+                if (visibleTaskCount > 0 &&
                     change.mode == TRANSIT_TO_BACK &&
                     taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
                     desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+                    desktopMixedTransitionHandler.addPendingMixedTransition(
+                        DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
+                            transition, taskInfo.taskId, visibleTaskCount == 1))
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4aeecbe..5276d9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -27,6 +27,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.AppCompatTaskInfo;
 import android.app.TaskInfo;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -176,12 +177,12 @@
     public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
             Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
             @PipAnimationController.TransitionDirection int direction, float startingAngle,
-            @Surface.Rotation int rotationDelta) {
+            @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) {
         if (mCurrentAnimator == null) {
             mCurrentAnimator = setupPipTransitionAnimator(
                     PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
                             endBounds, sourceHintRect, direction, 0 /* startingAngle */,
-                            rotationDelta));
+                            rotationDelta, alwaysAnimateTaskBounds));
         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
                 && mCurrentAnimator.isRunning()) {
             // If we are still animating the fade into pip, then just move the surface and ensure
@@ -197,7 +198,8 @@
             mCurrentAnimator.cancel();
             mCurrentAnimator = setupPipTransitionAnimator(
                     PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
-                            endBounds, sourceHintRect, direction, startingAngle, rotationDelta));
+                            endBounds, sourceHintRect, direction, startingAngle, rotationDelta,
+                            alwaysAnimateTaskBounds));
         }
         return mCurrentAnimator;
     }
@@ -585,28 +587,32 @@
         static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
                 Rect baseValue, Rect startValue, Rect endValue, Rect sourceRectHint,
                 @PipAnimationController.TransitionDirection int direction, float startingAngle,
-                @Surface.Rotation int rotationDelta) {
+                @Surface.Rotation int rotationDelta, boolean alwaysAnimateTaskBounds) {
             final boolean isOutPipDirection = isOutPipDirection(direction);
             final boolean isInPipDirection = isInPipDirection(direction);
             // Just for simplicity we'll interpolate between the source rect hint insets and empty
             // insets to calculate the window crop
             final Rect initialSourceValue;
             final Rect mainWindowFrame = taskInfo.topActivityMainWindowFrame;
-            final boolean hasNonMatchFrame = mainWindowFrame != null;
+            final AppCompatTaskInfo compatInfo = taskInfo.appCompatTaskInfo;
+            final boolean isSizeCompatOrLetterboxed = compatInfo.isTopActivityInSizeCompat()
+                    || compatInfo.isTopActivityLetterboxed();
+            // For the animation to swipe PIP to home or restore a PIP task from home, we don't
+            // override to the main window frame since we should animate the whole task.
+            final boolean shouldUseMainWindowFrame = mainWindowFrame != null
+                    && !alwaysAnimateTaskBounds && !isSizeCompatOrLetterboxed;
             final boolean changeOrientation =
                     rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270;
             final Rect baseBounds = new Rect(baseValue);
             final Rect startBounds = new Rect(startValue);
             final Rect endBounds = new Rect(endValue);
             if (isOutPipDirection) {
-                // TODO(b/356277166): handle rotation change with activity that provides main window
-                //  frame.
-                if (hasNonMatchFrame && !changeOrientation) {
+                if (shouldUseMainWindowFrame && !changeOrientation) {
                     endBounds.set(mainWindowFrame);
                 }
                 initialSourceValue = new Rect(endBounds);
             } else if (isInPipDirection) {
-                if (hasNonMatchFrame) {
+                if (shouldUseMainWindowFrame) {
                     baseBounds.set(mainWindowFrame);
                     if (startValue.equals(baseValue)) {
                         // If the start value is at initial state as in PIP animation, also override
@@ -635,9 +641,19 @@
             if (changeOrientation) {
                 lastEndRect = new Rect(endBounds);
                 rotatedEndRect = new Rect(endBounds);
-                // Rotate the end bounds according to the rotation delta because the display will
-                // be rotated to the same orientation.
-                rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
+                // TODO(b/375977163): polish the animation to restoring the PIP task back from
+                //  swipe-pip-to-home. Ideally we should send the transitionInfo after reparenting
+                //  the PIP activity back to the original task.
+                if (shouldUseMainWindowFrame) {
+                    // If we should animate the main window frame, set it to the rotatedRect
+                    // instead. The end bounds reported by transitionInfo is the bounds before
+                    // rotation, while main window frame is calculated after the rotation.
+                    rotatedEndRect.set(mainWindowFrame);
+                } else {
+                    // Rotate the end bounds according to the rotation delta because the display
+                    // will be rotated to the same orientation.
+                    rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
+                }
                 // Use the rect that has the same orientation as the hint rect.
                 initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue;
             } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 86c826a..30f1948 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1880,9 +1880,11 @@
                 ? mPipBoundsState.getBounds() : currentBounds;
         final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
                 && mPipAnimationController.getCurrentAnimator().isRunning();
+        // For resize animation, we always animate the whole PIP task bounds.
         final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
-                        sourceHintRect, direction, startingAngle, rotationDelta);
+                        sourceHintRect, direction, startingAngle, rotationDelta,
+                        true /* alwaysAnimateTaskBounds */);
         animator.setTransitionDirection(direction)
                 .setPipTransactionHandler(mPipTransactionHandler)
                 .setDuration(durationMs);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 8220ea5..f7aed44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -891,7 +891,8 @@
         final PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getAnimator(taskInfo, pipChange.getLeash(),
                         startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP,
-                        0 /* startingAngle */, pipRotateDelta);
+                        0 /* startingAngle */, pipRotateDelta,
+                        false /* alwaysAnimateTaskBounds */);
         animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration)
@@ -906,7 +907,7 @@
         final PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds,
                         endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP,
-                        0 /* startingAngle */, rotationDelta);
+                        0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */);
         animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
                 .setDuration(mEnterExitAnimationDuration);
         if (startTransaction != null) {
@@ -1102,8 +1103,6 @@
         if (taskInfo.pictureInPictureParams != null
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
-            // TODO(b/356277166): add support to swipe PIP to home with
-            //  non-match parent activity.
             handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
                     sourceHintRect, destinationBounds, taskInfo);
             return;
@@ -1125,7 +1124,7 @@
         if (enterAnimationType == ANIM_TYPE_BOUNDS) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
                     currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
-                    0 /* startingAngle */, rotationDelta);
+                    0 /* startingAngle */, rotationDelta, false /* alwaysAnimateTaskBounds */);
             if (sourceHintRect == null) {
                 // We use content overlay when there is no source rect hint to enter PiP use bounds
                 // animation. We also temporarily disallow app icon overlay and use color overlay
@@ -1248,10 +1247,14 @@
         // to avoid flicker.
         final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets);
         pipTaskInfo.displayCutoutInsets.setEmpty();
+        // Always use the task bounds even if the PIP activity doesn't match parent because the app
+        // and the whole task will move behind. We should animate the whole task bounds in this
+        // case.
         final PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
                         destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
-                        0 /* startingAngle */, ROTATION_0 /* rotationDelta */)
+                        0 /* startingAngle */, ROTATION_0 /* rotationDelta */,
+                        true /* alwaysAnimateTaskBounds */)
                         .setPipTransactionHandler(mTransactionConsumer)
                         .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
         // The start state is the end state for swipe-auto-pip.
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 19a73f3..cc0e1df 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
@@ -55,6 +55,7 @@
 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.ENTER_REASON_LAUNCHER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
@@ -1098,11 +1099,16 @@
 
     void setSideStagePosition(@SplitPosition int sideStagePosition,
             @Nullable WindowContainerTransaction wct) {
+        setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
+    }
+
+    private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
+            @Nullable WindowContainerTransaction wct) {
         if (mSideStagePosition == sideStagePosition) return;
         mSideStagePosition = sideStagePosition;
         sendOnStagePositionChanged();
 
-        if (mSideStage.mVisible) {
+        if (mSideStage.mVisible && updateBounds) {
             if (wct == null) {
                 // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
                 onLayoutSizeChanged(mSplitLayout);
@@ -1193,7 +1199,6 @@
         if (!isSplitActive()) return;
 
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
         applyExitSplitScreen(childrenToTop, wct, exitReason);
     }
 
@@ -1593,13 +1598,6 @@
         }
         if (present) {
             updateRecentTasksSplitPair();
-        } else if (mMainStage.getChildCount() == 0 && mSideStage.getChildCount() == 0) {
-            mRecentTasks.ifPresent(recentTasks -> {
-                // remove the split pair mapping from recentTasks, and disable further updates
-                // to splits in the recents until we enter split again.
-                recentTasks.removeSplitPair(taskId);
-            });
-            exitSplitScreen(mMainStage, EXIT_REASON_ROOT_TASK_VANISHED);
         }
 
         for (int i = mListeners.size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 29e4b5b..9fcf98b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -353,7 +353,7 @@
             boolean isSeamlessDisplayChange = false;
 
             if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
-                if (info.getType() == TRANSIT_CHANGE) {
+                if (info.getType() == TRANSIT_CHANGE || isOnlyTranslucent) {
                     final int anim = getRotationAnimationHint(change, info, mDisplayController);
                     isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
                     if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
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 a2d81a0..d71e61a 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
@@ -782,7 +782,8 @@
             mTaskToken = taskInfo.token;
             mDragPositioningCallback = dragPositioningCallback;
             final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
-            final long appHandleHoldToDragDuration = Flags.enableHoldToDragAppHandle()
+            final long appHandleHoldToDragDuration =
+                    DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue()
                     ? APP_HANDLE_HOLD_TO_DRAG_DURATION_MS : 0;
             mHandleDragDetector = new DragDetector(this, appHandleHoldToDragDuration,
                     touchSlop);
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index 4fe66f3..4cddf31 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -23,8 +23,9 @@
 import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
 import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
 import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
-import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
 import android.tools.flicker.assertors.assertions.AppWindowAlignsWithOnlyOneDisplayCornerAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowBecomesInvisible
+import android.tools.flicker.assertors.assertions.AppWindowBecomesVisible
 import android.tools.flicker.assertors.assertions.AppWindowCoversLeftHalfScreenAtEnd
 import android.tools.flicker.assertors.assertions.AppWindowCoversRightHalfScreenAtEnd
 import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
@@ -44,6 +45,7 @@
 import android.tools.flicker.config.AssertionTemplates
 import android.tools.flicker.config.FlickerConfigEntry
 import android.tools.flicker.config.ScenarioId
+import android.tools.flicker.config.common.Components.LAUNCHER
 import android.tools.flicker.config.desktopmode.Components.DESKTOP_MODE_APP
 import android.tools.flicker.config.desktopmode.Components.DESKTOP_WALLPAPER
 import android.tools.flicker.config.desktopmode.Components.NON_RESIZABLE_APP
@@ -365,5 +367,57 @@
                             AppWindowAlignsWithOnlyOneDisplayCornerAtEnd(DESKTOP_MODE_APP)
                         ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
+
+        val MINIMIZE_APP =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("MINIMIZE_APP"),
+                extractor =
+                ShellTransitionScenarioExtractor(
+                    transitionMatcher =
+                    object : ITransitionMatcher {
+                        override fun findAll(
+                            transitions: Collection<Transition>
+                        ): Collection<Transition> {
+                            return transitions
+                                .filter { it.type == TransitionType.MINIMIZE }
+                                .sortedByDescending { it.id }
+                                .drop(1)
+                        }
+                    }
+                ),
+                assertions =
+                AssertionTemplates.COMMON_ASSERTIONS +
+                    listOf(
+                        AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+                        AppWindowBecomesInvisible(DESKTOP_MODE_APP),
+                    ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+            )
+
+        val MINIMIZE_LAST_APP =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("MINIMIZE_LAST_APP"),
+                extractor =
+                ShellTransitionScenarioExtractor(
+                    transitionMatcher =
+                    object : ITransitionMatcher {
+                        override fun findAll(
+                            transitions: Collection<Transition>
+                        ): Collection<Transition> {
+                            val lastTransition =
+                                transitions
+                                .filter { it.type == TransitionType.MINIMIZE }
+                                .maxByOrNull { it.id }!!
+                            return listOf(lastTransition)
+                        }
+                    }
+                ),
+                assertions =
+                AssertionTemplates.COMMON_ASSERTIONS +
+                    listOf(
+                        AppWindowOnTopAtStart(DESKTOP_MODE_APP),
+                        AppWindowBecomesInvisible(DESKTOP_MODE_APP),
+                        AppWindowOnTopAtEnd(LAUNCHER),
+                    ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+            )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt
new file mode 100644
index 0000000..58582b0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsLandscape.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.Rotation.ROTATION_90
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Minimize app windows by pressing the minimize button.
+ *
+ * Assert that the app windows gets hidden.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MinimizeAppsLandscape : MinimizeAppWindows(rotation = ROTATION_90) {
+    @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"])
+    @Test
+    override fun minimizeAllAppWindows() = super.minimizeAllAppWindows()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig()
+                .use(FlickerServiceConfig.DEFAULT)
+                .use(MINIMIZE_APP)
+                .use(MINIMIZE_LAST_APP)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt
new file mode 100644
index 0000000..7970426
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/MinimizeAppsPortrait.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_APP
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.MINIMIZE_LAST_APP
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Minimize app windows by pressing the minimize button.
+ *
+ * Assert that the app windows gets hidden.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class MinimizeAppsPortrait : MinimizeAppWindows() {
+    @ExpectedScenarios(["MINIMIZE_APP", "MINIMIZE_LAST_APP"])
+    @Test
+    override fun minimizeAllAppWindows() = super.minimizeAllAppWindows()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig()
+                .use(FlickerServiceConfig.DEFAULT)
+                .use(MINIMIZE_APP)
+                .use(MINIMIZE_LAST_APP)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
index 824c448..f442fdb 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt
@@ -18,6 +18,7 @@
 
 import android.tools.NavBar
 import android.tools.Rotation
+import com.android.internal.R
 import com.android.window.flags.Flags
 import com.android.wm.shell.Utils
 import org.junit.After
@@ -40,6 +41,9 @@
     @Before
     fun setup() {
         Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        // Skip the test when the drag-to-maximize is enabled on this device.
+        Assume.assumeFalse(Flags.enableDragToMaximize() &&
+            instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
         testApp.enterDesktopWithDrag(wmHelper, device)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt
new file mode 100644
index 0000000..6df8d6f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WindowingMode
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.ShellExecutor
+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.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() {
+
+    private val testExecutor = mock<ShellExecutor>()
+    private val closingTaskLeash = mock<SurfaceControl>()
+    private val displayController = mock<DisplayController>()
+
+    private lateinit var handler: DesktopBackNavigationTransitionHandler
+
+    @Before
+    fun setUp() {
+        handler =
+            DesktopBackNavigationTransitionHandler(
+                testExecutor,
+                testExecutor,
+                displayController
+            )
+        whenever(displayController.getDisplayContext(any())).thenReturn(mContext)
+    }
+
+    @Test
+    fun handleRequest_returnsNull() {
+        assertNull(handler.handleRequest(mock(), mock()))
+    }
+
+    @Test
+    fun startAnimation_openTransition_returnsFalse() {
+        val animates =
+            handler.startAnimation(
+                transition = mock(),
+                info =
+                createTransitionInfo(
+                    type = WindowManager.TRANSIT_OPEN,
+                    task = createTask(WINDOWING_MODE_FREEFORM)
+                ),
+                startTransaction = mock(),
+                finishTransaction = mock(),
+                finishCallback = {}
+            )
+
+        assertFalse("Should not animate open transition", animates)
+    }
+
+    @Test
+    fun startAnimation_toBackTransitionFullscreenTask_returnsFalse() {
+        val animates =
+            handler.startAnimation(
+                transition = mock(),
+                info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)),
+                startTransaction = mock(),
+                finishTransaction = mock(),
+                finishCallback = {}
+            )
+
+        assertFalse("Should not animate fullscreen task to back transition", animates)
+    }
+
+    @Test
+    fun startAnimation_toBackTransitionOpeningFreeformTask_returnsFalse() {
+        val animates =
+            handler.startAnimation(
+                transition = mock(),
+                info =
+                createTransitionInfo(
+                    changeMode = WindowManager.TRANSIT_OPEN,
+                    task = createTask(WINDOWING_MODE_FREEFORM)
+                ),
+                startTransaction = mock(),
+                finishTransaction = mock(),
+                finishCallback = {}
+            )
+
+        assertFalse("Should not animate opening freeform task to back transition", animates)
+    }
+
+    @Test
+    fun startAnimation_toBackTransitionToBackFreeformTask_returnsTrue() {
+        val animates =
+            handler.startAnimation(
+                transition = mock(),
+                info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)),
+                startTransaction = mock(),
+                finishTransaction = mock(),
+                finishCallback = {}
+            )
+
+        assertTrue("Should animate going to back freeform task close transition", animates)
+    }
+
+    @Test
+    fun startAnimation_closeTransitionClosingFreeformTask_returnsTrue() {
+        val animates =
+            handler.startAnimation(
+                transition = mock(),
+                info = createTransitionInfo(
+                    type = TRANSIT_CLOSE,
+                    changeMode = TRANSIT_CLOSE,
+                    task = createTask(WINDOWING_MODE_FREEFORM)
+                ),
+                startTransaction = mock(),
+                finishTransaction = mock(),
+                finishCallback = {}
+            )
+
+        assertTrue("Should animate going to back freeform task close transition", animates)
+    }
+    private fun createTransitionInfo(
+        type: Int = WindowManager.TRANSIT_TO_BACK,
+        changeMode: Int = WindowManager.TRANSIT_TO_BACK,
+        task: RunningTaskInfo
+    ): TransitionInfo =
+        TransitionInfo(type, 0 /* flags */).apply {
+            addChange(
+                TransitionInfo.Change(mock(), closingTaskLeash).apply {
+                    mode = changeMode
+                    parent = null
+                    taskInfo = task
+                }
+            )
+        }
+
+    private fun createTask(@WindowingMode windowingMode: Int): RunningTaskInfo =
+        TestRunningTaskInfoBuilder()
+            .setActivityType(ACTIVITY_TYPE_STANDARD)
+            .setWindowingMode(windowingMode)
+            .build()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index b06c2da..f21f264 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -32,6 +32,7 @@
 import android.view.SurfaceControl
 import android.view.WindowManager
 import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_BACK
 import android.view.WindowManager.TransitionType
 import android.window.TransitionInfo
 import android.window.WindowContainerTransaction
@@ -77,16 +78,28 @@
 
     @JvmField @Rule val setFlagsRule = SetFlagsRule()
 
-    @Mock lateinit var transitions: Transitions
-    @Mock lateinit var desktopRepository: DesktopRepository
-    @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
-    @Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
-    @Mock lateinit var desktopImmersiveController: DesktopImmersiveController
-    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
-    @Mock lateinit var mockHandler: Handler
-    @Mock lateinit var closingTaskLeash: SurfaceControl
-    @Mock lateinit var shellInit: ShellInit
-    @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+    @Mock
+    lateinit var transitions: Transitions
+    @Mock
+    lateinit var desktopRepository: DesktopRepository
+    @Mock
+    lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
+    @Mock
+    lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
+    @Mock
+    lateinit var desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler
+    @Mock
+    lateinit var desktopImmersiveController: DesktopImmersiveController
+    @Mock
+    lateinit var interactionJankMonitor: InteractionJankMonitor
+    @Mock
+    lateinit var mockHandler: Handler
+    @Mock
+    lateinit var closingTaskLeash: SurfaceControl
+    @Mock
+    lateinit var shellInit: ShellInit
+    @Mock
+    lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
 
     private lateinit var mixedHandler: DesktopMixedTransitionHandler
 
@@ -100,6 +113,7 @@
                 freeformTaskTransitionHandler,
                 closeDesktopTaskTransitionHandler,
                 desktopImmersiveController,
+                desktopBackNavigationTransitionHandler,
                 interactionJankMonitor,
                 mockHandler,
                 shellInit,
@@ -595,6 +609,87 @@
         assertThat(mixedHandler.pendingMixedTransitions).isEmpty()
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+    fun startAnimation_withMinimizingDesktopTask_callsBackNavigationHandler() {
+        val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val transition = Binder()
+        whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
+        whenever(
+            desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any())
+        )
+            .thenReturn(true)
+        mixedHandler.addPendingMixedTransition(
+            PendingMixedTransition.Minimize(
+                transition = transition,
+                minimizingTask = minimizingTask.taskId,
+                isLastTask = false,
+            )
+        )
+
+        val minimizingTaskChange = createChange(minimizingTask)
+        val started = mixedHandler.startAnimation(
+            transition = transition,
+            info =
+                createTransitionInfo(
+                TRANSIT_TO_BACK,
+                listOf(minimizingTaskChange)
+            ),
+            startTransaction = mock(),
+            finishTransaction = mock(),
+            finishCallback = {}
+        )
+
+        assertTrue("Should delegate animation to back navigation transition handler", started)
+        verify(desktopBackNavigationTransitionHandler)
+            .startAnimation(
+                eq(transition),
+                argThat { info -> info.changes.contains(minimizingTaskChange) },
+                any(), any(), any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+    fun startAnimation_withMinimizingLastDesktopTask_dispatchesTransition() {
+        val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val transition = Binder()
+        whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
+        whenever(
+            desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any())
+        )
+            .thenReturn(true)
+        mixedHandler.addPendingMixedTransition(
+            PendingMixedTransition.Minimize(
+                transition = transition,
+                minimizingTask = minimizingTask.taskId,
+                isLastTask = true,
+            )
+        )
+
+        val minimizingTaskChange = createChange(minimizingTask)
+        mixedHandler.startAnimation(
+            transition = transition,
+            info =
+            createTransitionInfo(
+                TRANSIT_TO_BACK,
+                listOf(minimizingTaskChange)
+            ),
+            startTransaction = mock(),
+            finishTransaction = mock(),
+            finishCallback = {}
+        )
+
+        verify(transitions)
+            .dispatchTransition(
+                eq(transition),
+                argThat { info -> info.changes.contains(minimizingTaskChange) },
+                any(),
+                any(),
+                any(),
+                eq(mixedHandler)
+            )
+    }
+
     private fun createTransitionInfo(
         type: Int = WindowManager.TRANSIT_CLOSE,
         changeMode: Int = WindowManager.TRANSIT_CLOSE,
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 315a46f..ad266ea 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
@@ -3038,6 +3038,21 @@
     // Assert bounds set to stable bounds
     val wct = getLatestToggleResizeDesktopTaskWct()
     assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+    // Assert event is properly logged
+    verify(desktopModeEventLogger, times(1)).logTaskResizingStarted(
+      ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+      motionEvent,
+      task,
+      displayController
+    )
+    verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
+      ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+      motionEvent,
+      task,
+      STABLE_BOUNDS.height(),
+      STABLE_BOUNDS.width(),
+      displayController
+    )
   }
 
   @Test
@@ -3082,6 +3097,13 @@
       eq(STABLE_BOUNDS),
       anyOrNull(),
     )
+    // Assert no event is logged
+    verify(desktopModeEventLogger, never()).logTaskResizingStarted(
+      any(), any(), any(), any(), any()
+    )
+    verify(desktopModeEventLogger, never()).logTaskResizingEnded(
+      any(), any(), any(), any(), any(), any(), any()
+    )
   }
 
   @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 737439c..7f1c1db 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -76,6 +76,7 @@
     private val context = mock<Context>()
     private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
     private val taskRepository = mock<DesktopRepository>()
+    private val mixedHandler = mock<DesktopMixedTransitionHandler>()
 
     private lateinit var transitionObserver: DesktopTasksTransitionObserver
     private lateinit var shellInit: ShellInit
@@ -87,7 +88,7 @@
 
         transitionObserver =
             DesktopTasksTransitionObserver(
-                context, taskRepository, transitions, shellTaskOrganizer, shellInit
+                context, taskRepository, transitions, shellTaskOrganizer, mixedHandler, shellInit
             )
     }
 
@@ -106,6 +107,7 @@
         )
 
         verify(taskRepository).minimizeTask(task.displayId, task.taskId)
+        verify(mixedHandler).addPendingMixedTransition(any())
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 72950a8..6d37ed7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -28,8 +28,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import android.app.AppCompatTaskInfo;
 import android.app.TaskInfo;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
@@ -75,6 +78,7 @@
                 .setContainerLayer()
                 .setName("FakeLeash")
                 .build();
+        mTaskInfo.appCompatTaskInfo = mock(AppCompatTaskInfo.class);
     }
 
     @Test
@@ -93,7 +97,8 @@
         final Rect endValue1 = new Rect(100, 100, 200, 200);
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
-                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+                        false /* alwaysAnimateTaskBounds */);
 
         assertEquals("Expect ANIM_TYPE_BOUNDS animation",
                 animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -107,14 +112,16 @@
         final Rect endValue2 = new Rect(200, 200, 300, 300);
         final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
-                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+                        false /* alwaysAnimateTaskBounds */);
         oldAnimator.setSurfaceControlTransactionFactory(
                 MockSurfaceControlHelper::createMockSurfaceControlTransaction);
         oldAnimator.start();
 
         final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null,
-                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+                        false /* alwaysAnimateTaskBounds */);
 
         assertEquals("getAnimator with same type returns same animator",
                 oldAnimator, newAnimator);
@@ -145,7 +152,8 @@
         // Fullscreen to PiP.
         PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
-                        TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90);
+                        TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
+                        false /* alwaysAnimateTaskBounds */);
         // Apply fraction 1 to compute the end value.
         animator.applySurfaceControlTransaction(mLeash, tx, 1);
         final Rect rotatedEndBounds = new Rect(endBounds);
@@ -157,7 +165,8 @@
         startBounds.set(0, 0, 1000, 500);
         endBounds.set(200, 100, 400, 500);
         animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
-                endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270);
+                endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
+                false /* alwaysAnimateTaskBounds */);
         animator.applySurfaceControlTransaction(mLeash, tx, 1);
         rotatedEndBounds.set(endBounds);
         rotateBounds(rotatedEndBounds, startBounds, ROTATION_270);
@@ -166,6 +175,37 @@
     }
 
     @Test
+    public void pipTransitionAnimator_rotatedEndValue_overrideMainWindowFrame() {
+        final SurfaceControl.Transaction tx = createMockSurfaceControlTransaction();
+        final Rect startBounds = new Rect(200, 700, 400, 800);
+        final Rect endBounds = new Rect(0, 0, 500, 1000);
+        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);
+
+        // Fullscreen task to PiP.
+        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+                .getAnimator(mTaskInfo, mLeash, null, startBounds, endBounds, null,
+                        TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_90,
+                        false /* alwaysAnimateTaskBounds */);
+        // Apply fraction 1 to compute the end value.
+        animator.applySurfaceControlTransaction(mLeash, tx, 1);
+
+        assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame,
+                animator.mCurrentValue);
+
+        // PiP to fullscreen.
+        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 250, 1000, 500);
+        startBounds.set(0, 0, 1000, 500);
+        endBounds.set(200, 100, 400, 500);
+        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, startBounds, startBounds,
+                endBounds, null, TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_270,
+                false /* alwaysAnimateTaskBounds */);
+        animator.applySurfaceControlTransaction(mLeash, tx, 1);
+
+        assertEquals("Expect use main window frame", mTaskInfo.topActivityMainWindowFrame,
+                animator.mCurrentValue);
+    }
+
+    @Test
     @SuppressWarnings("unchecked")
     public void pipTransitionAnimator_updateEndValue() {
         final Rect baseValue = new Rect(0, 0, 100, 100);
@@ -174,7 +214,8 @@
         final Rect endValue2 = new Rect(200, 200, 300, 300);
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
-                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+                        false /* alwaysAnimateTaskBounds */);
 
         animator.updateEndValue(endValue2);
 
@@ -188,7 +229,8 @@
         final Rect endValue = new Rect(100, 100, 200, 200);
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
                 .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
-                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
+                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+                        false /* alwaysAnimateTaskBounds */);
         animator.setSurfaceControlTransactionFactory(
                 MockSurfaceControlHelper::createMockSurfaceControlTransaction);
 
@@ -207,4 +249,126 @@
         verify(mPipAnimationCallback).onPipAnimationEnd(eq(mTaskInfo),
                 any(SurfaceControl.Transaction.class), eq(animator));
     }
+
+    @Test
+    public void pipTransitionAnimator_overrideMainWindowFrame() {
+        final Rect baseValue = new Rect(0, 0, 100, 100);
+        final Rect startValue = new Rect(0, 0, 100, 100);
+        final Rect endValue = new Rect(100, 100, 200, 200);
+        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+                        false /* alwaysAnimateTaskBounds */);
+
+        assertEquals("Expect base value is overridden for in-PIP transition",
+                mTaskInfo.topActivityMainWindowFrame, animator.getBaseValue());
+        assertEquals("Expect start value is overridden for in-PIP transition",
+                mTaskInfo.topActivityMainWindowFrame, animator.getStartValue());
+        assertEquals("Expect end value is not overridden for in-PIP transition",
+                endValue, animator.getEndValue());
+
+        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+                endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+                        false /* alwaysAnimateTaskBounds */);
+
+        assertEquals("Expect base value is not overridden for leave-PIP transition",
+                baseValue, animator.getBaseValue());
+        assertEquals("Expect start value is not overridden for leave-PIP transition",
+                startValue, animator.getStartValue());
+        assertEquals("Expect end value is overridden for leave-PIP transition",
+                mTaskInfo.topActivityMainWindowFrame, animator.getEndValue());
+    }
+
+    @Test
+    public void pipTransitionAnimator_animateTaskBounds() {
+        final Rect baseValue = new Rect(0, 0, 100, 100);
+        final Rect startValue = new Rect(0, 0, 100, 100);
+        final Rect endValue = new Rect(100, 100, 200, 200);
+        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+                        true /* alwaysAnimateTaskBounds */);
+
+        assertEquals("Expect base value is not overridden for in-PIP transition",
+                baseValue, animator.getBaseValue());
+        assertEquals("Expect start value is not overridden for in-PIP transition",
+                startValue, animator.getStartValue());
+        assertEquals("Expect end value is not overridden for in-PIP transition",
+                endValue, animator.getEndValue());
+
+        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+                endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+                true /* alwaysAnimateTaskBounds */);
+
+        assertEquals("Expect base value is not overridden for leave-PIP transition",
+                baseValue, animator.getBaseValue());
+        assertEquals("Expect start value is not overridden for leave-PIP transition",
+                startValue, animator.getStartValue());
+        assertEquals("Expect end value is not overridden for leave-PIP transition",
+                endValue, animator.getEndValue());
+    }
+
+    @Test
+    public void pipTransitionAnimator_letterboxed_animateTaskBounds() {
+        final Rect baseValue = new Rect(0, 0, 100, 100);
+        final Rect startValue = new Rect(0, 0, 100, 100);
+        final Rect endValue = new Rect(100, 100, 200, 200);
+        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+        doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityLetterboxed();
+        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+                        false /* alwaysAnimateTaskBounds */);
+
+        assertEquals("Expect base value is not overridden for in-PIP transition",
+                baseValue, animator.getBaseValue());
+        assertEquals("Expect start value is not overridden for in-PIP transition",
+                startValue, animator.getStartValue());
+        assertEquals("Expect end value is not overridden for in-PIP transition",
+                endValue, animator.getEndValue());
+
+        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+                endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+                false /* alwaysAnimateTaskBounds */);
+
+        assertEquals("Expect base value is not overridden for leave-PIP transition",
+                baseValue, animator.getBaseValue());
+        assertEquals("Expect start value is not overridden for leave-PIP transition",
+                startValue, animator.getStartValue());
+        assertEquals("Expect end value is not overridden for leave-PIP transition",
+                endValue, animator.getEndValue());
+    }
+
+    @Test
+    public void pipTransitionAnimator_sizeCompat_animateTaskBounds() {
+        final Rect baseValue = new Rect(0, 0, 100, 100);
+        final Rect startValue = new Rect(0, 0, 100, 100);
+        final Rect endValue = new Rect(100, 100, 200, 200);
+        mTaskInfo.topActivityMainWindowFrame = new Rect(0, 50, 100, 100);
+        doReturn(true).when(mTaskInfo.appCompatTaskInfo).isTopActivityInSizeCompat();
+        PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
+                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
+                        TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0,
+                        false /* alwaysAnimateTaskBounds */);
+
+        assertEquals("Expect base value is not overridden for in-PIP transition",
+                baseValue, animator.getBaseValue());
+        assertEquals("Expect start value is not overridden for in-PIP transition",
+                startValue, animator.getStartValue());
+        assertEquals("Expect end value is not overridden for in-PIP transition",
+                endValue, animator.getEndValue());
+
+        animator = mPipAnimationController.getAnimator(mTaskInfo, mLeash, baseValue, startValue,
+                endValue, null, TRANSITION_DIRECTION_LEAVE_PIP, 0, ROTATION_0,
+                false /* alwaysAnimateTaskBounds */);
+
+        assertEquals("Expect base value is not overridden for leave-PIP transition",
+                baseValue, animator.getBaseValue());
+        assertEquals("Expect start value is not overridden for leave-PIP transition",
+                startValue, animator.getStartValue());
+        assertEquals("Expect end value is not overridden for leave-PIP transition",
+                endValue, animator.getEndValue());
+    }
 }
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index fddcf29..5f84f47 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -33,9 +33,9 @@
 #endif  // __ANDROID__
 }
 
-inline bool typeface_redesign() {
+inline bool typeface_redesign_readonly() {
 #ifdef __ANDROID__
-    static bool flag = com_android_text_flags_typeface_redesign();
+    static bool flag = com_android_text_flags_typeface_redesign_readonly();
     return flag;
 #else
     return true;
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 1510ce1..20acf98 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -73,7 +73,7 @@
     static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
         float saveSkewX = paint->getSkFont().getSkewX();
         bool savefakeBold = paint->getSkFont().isEmbolden();
-        if (text_feature::typeface_redesign()) {
+        if (text_feature::typeface_redesign_readonly()) {
             for (uint32_t runIdx = 0; runIdx < layout.getFontRunCount(); ++runIdx) {
                 uint32_t start = layout.getFontRunStart(runIdx);
                 uint32_t end = layout.getFontRunEnd(runIdx);
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 70e6bed..5f69346 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -86,7 +86,7 @@
         overallDescent = std::max(overallDescent, extent.descent);
     }
 
-    if (text_feature::typeface_redesign()) {
+    if (text_feature::typeface_redesign_readonly()) {
         uint32_t runCount = layout.getFontRunCount();
 
         std::unordered_map<minikin::FakedFont, uint32_t, FakedFontKey> fakedToFontIds;
@@ -229,7 +229,7 @@
 // CriticalNative
 static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
     const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
-    if (text_feature::typeface_redesign()) {
+    if (text_feature::typeface_redesign_readonly()) {
         float value =
                 findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght);
         return std::isnan(value) ? NO_OVERRIDE : value;
@@ -241,7 +241,7 @@
 // CriticalNative
 static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
     const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
-    if (text_feature::typeface_redesign()) {
+    if (text_feature::typeface_redesign_readonly()) {
         float value =
                 findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital);
         return std::isnan(value) ? NO_OVERRIDE : value;
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index e575dae..2ae89d3 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -18,6 +18,7 @@
 
 import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.codec.Flags.FLAG_SUBSESSION_METRICS;
 
 import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
 
@@ -890,7 +891,7 @@
  any start codes), and submit it as a <strong>regular</strong> input buffer.
  <p>
  You will receive an {@link #INFO_OUTPUT_FORMAT_CHANGED} return value from {@link
- #dequeueOutputBuffer dequeueOutputBuffer} or a {@link Callback#onOutputBufferAvailable
+ #dequeueOutputBuffer dequeueOutputBuffer} or a {@link Callback#onOutputFormatChanged
  onOutputFormatChanged} callback just after the picture-size change takes place and before any
  frames with the new size have been returned.
  <p class=note>
@@ -1835,6 +1836,13 @@
     private static final int CB_CRYPTO_ERROR = 6;
     private static final int CB_LARGE_FRAME_OUTPUT_AVAILABLE = 7;
 
+    /**
+     * Callback ID for when the metrics for this codec have been flushed due to
+     * the start of a new subsession. The associated Java Message object will
+     * contain the flushed metrics as a PersistentBundle in the obj field.
+     */
+    private static final int CB_METRICS_FLUSHED = 8;
+
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
 
@@ -2007,6 +2015,15 @@
                     break;
                 }
 
+                case CB_METRICS_FLUSHED:
+                {
+
+                    if (GetFlag(() -> android.media.codec.Flags.subsessionMetrics())) {
+                        mCallback.onMetricsFlushed(mCodec, (PersistableBundle)msg.obj);
+                    }
+                    break;
+                }
+
                 default:
                 {
                     break;
@@ -4958,14 +4975,24 @@
     public native final String getCanonicalName();
 
     /**
-     *  Return Metrics data about the current codec instance.
+     * Return Metrics data about the current codec instance.
+     * <p>
+     * Call this method after configuration, during execution, or after
+     * the codec has been already stopped.
+     * <p>
+     * Beginning with {@link android.os.Build.VERSION_CODES#B}
+     * this method can be used to get the Metrics data prior to an error.
+     * (e.g. in {@link Callback#onError} or after a method throws
+     * {@link MediaCodec.CodecException}.) Before that, the Metrics data was
+     * cleared on error, resulting in a null return value.
      *
      * @return a {@link PersistableBundle} containing the set of attributes and values
      * available for the media being handled by this instance of MediaCodec
      * The attributes are descibed in {@link MetricsConstants}.
      *
      * Additional vendor-specific fields may also be present in
-     * the return value.
+     * the return value. Returns null if there is no Metrics data.
+     *
      */
     public PersistableBundle getMetrics() {
         PersistableBundle bundle = native_getMetrics();
@@ -5692,6 +5719,27 @@
          */
         public abstract void onOutputFormatChanged(
                 @NonNull MediaCodec codec, @NonNull MediaFormat format);
+
+        /**
+         * Called when the metrics for this codec have been flushed due to the
+         * start of a new subsession.
+         * <p>
+         * This can happen when the codec is reconfigured after stop(), or
+         * mid-stream e.g. if the video size changes. When this happens, the
+         * metrics for the previous subsession are flushed, and
+         * {@link MediaCodec#getMetrics} will return the metrics for the
+         * new subsession. This happens just before the {@link Callback#onOutputFormatChanged}
+         * event, so this <b>optional</b> callback is provided to be able to
+         * capture the final metrics for the previous subsession.
+         *
+         * @param codec The MediaCodec object.
+         * @param metrics The flushed metrics for this codec.
+         */
+        @FlaggedApi(FLAG_SUBSESSION_METRICS)
+        public void onMetricsFlushed(
+                @NonNull MediaCodec codec, @NonNull PersistableBundle metrics) {
+            // default implementation ignores this callback.
+        }
     }
 
     private void postEventFromNative(
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index edd49c5..0209eb8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.ServiceConnection
+import android.os.DeadObjectException
 import android.os.IBinder
 import android.os.IInterface
 import android.os.RemoteException
@@ -52,6 +53,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.emitAll
 import kotlinx.coroutines.flow.filterIsInstance
@@ -304,6 +306,14 @@
                 service.registerDeviceSettingsListener(deviceInfo, listener)
                 awaitClose { service.unregisterDeviceSettingsListener(deviceInfo, listener) }
             }
+            .catch { e ->
+                if (e is DeadObjectException) {
+                    Log.e(TAG, "DeadObjectException happens when registering listener.", e)
+                    emit(listOf())
+                } else {
+                    throw e
+                }
+            }
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
     }
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 207ed71..3df9603 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -267,7 +267,7 @@
 flag {
     name: "dual_shade"
     namespace: "systemui"
-    description: "Enables the BC25 Dual Shade (go/bc25-dual-shade-design)."
+    description: "Enables Dual Shade (go/dual-shade-design-doc)."
     bug: "337259436"
 }
 
@@ -1360,16 +1360,6 @@
 }
 
 flag {
-  name: "notification_pulsing_fix"
-  namespace: "systemui"
-  description: "Allow showing new pulsing notifications when the device is already pulsing."
-  bug: "335560575"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
     name: "media_lockscreen_launch_animation"
     namespace : "systemui"
     description : "Enable the origin launch animation for UMO when opening on top of lockscreen."
@@ -1784,3 +1774,13 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "keyguard_transition_force_finish_on_screen_off"
+    namespace: "systemui"
+    description: "Forces KTF transitions to finish if the screen turns all the way off."
+    bug: "331636736"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
new file mode 100644
index 0000000..651e401
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..10e630d
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-sw600dp/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
+    <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index c574d1f..7feea6e 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -33,4 +33,10 @@
     <dimen name="small_clock_height">114dp</dimen>
     <dimen name="small_clock_padding_top">28dp</dimen>
     <dimen name="clock_padding_start">28dp</dimen>
+
+    <!-- When large clock is showing, offset the smartspace by this amount -->
+    <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+    <!--Dimens used in both lockscreen  preview and smartspace -->
+    <dimen name="date_weather_view_height">24dp</dimen>
+    <dimen name="enhanced_smartspace_height">104dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index a4782ac..ee21ea6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -55,10 +55,7 @@
     override val view: View
         get() = layerController.view
 
-    override val config =
-        ClockFaceConfig(
-            hasCustomPositionUpdatedAnimation = false // TODO(b/364673982)
-        )
+    override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)
 
     override var theme = ThemeConfig(true, assets.seedColor)
 
@@ -96,6 +93,19 @@
         layerController.view.layoutParams = lp
     }
 
+    /** See documentation at [FlexClockView.offsetGlyphsForStepClockAnimation]. */
+    private fun offsetGlyphsForStepClockAnimation(
+        clockStartLeft: Int,
+        direction: Int,
+        fraction: Float
+    ) {
+        (view as? FlexClockView)?.offsetGlyphsForStepClockAnimation(
+            clockStartLeft,
+            direction,
+            fraction,
+        )
+    }
+
     override val layout: ClockFaceLayout =
         DefaultClockFaceLayout(view).apply {
             views[0].id =
@@ -248,10 +258,12 @@
 
             override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
                 layerController.animations.onPositionUpdated(fromLeft, direction, fraction)
+                if (isLargeClock) offsetGlyphsForStepClockAnimation(fromLeft, direction, fraction)
             }
 
             override fun onPositionUpdated(distance: Float, fraction: Float) {
                 layerController.animations.onPositionUpdated(distance, fraction)
+                // TODO(b/378128811) port stepping animation
             }
         }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index d86c0d6..593eba9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.graphics.Canvas
 import android.graphics.Point
+import android.util.MathUtils.constrainedMap
 import android.view.View
 import android.view.ViewGroup
 import android.widget.RelativeLayout
@@ -50,6 +51,8 @@
             )
     }
 
+    private val digitOffsets = mutableMapOf<Int, Float>()
+
     override fun addView(child: View?) {
         super.addView(child)
         (child as SimpleDigitalClockTextView).digitTranslateAnimator =
@@ -76,7 +79,7 @@
         digitLeftTopMap[R.id.HOUR_SECOND_DIGIT] = Point(maxSingleDigitSize.x, 0)
         digitLeftTopMap[R.id.MINUTE_FIRST_DIGIT] = Point(0, maxSingleDigitSize.y)
         digitLeftTopMap[R.id.MINUTE_SECOND_DIGIT] = Point(maxSingleDigitSize)
-        digitLeftTopMap.forEach { _, point ->
+        digitLeftTopMap.forEach { (_, point) ->
             point.x += abs(aodTranslate.x)
             point.y += abs(aodTranslate.y)
         }
@@ -89,11 +92,17 @@
 
     override fun onDraw(canvas: Canvas) {
         super.onDraw(canvas)
-        digitalClockTextViewMap.forEach { (id, _) ->
-            val textView = digitalClockTextViewMap[id]!!
-            canvas.translate(digitLeftTopMap[id]!!.x.toFloat(), digitLeftTopMap[id]!!.y.toFloat())
+        digitalClockTextViewMap.forEach { (id, textView) ->
+            // save canvas location in anticipation of restoration later
+            canvas.save()
+            val xTranslateAmount =
+                digitOffsets.getOrDefault(id, 0f) + digitLeftTopMap[id]!!.x.toFloat()
+            // move canvas to location that the textView would like
+            canvas.translate(xTranslateAmount, digitLeftTopMap[id]!!.y.toFloat())
+            // draw the textView at the location of the canvas above
             textView.draw(canvas)
-            canvas.translate(-digitLeftTopMap[id]!!.x.toFloat(), -digitLeftTopMap[id]!!.y.toFloat())
+            // reset the canvas location back to 0 without drawing
+            canvas.restore()
         }
     }
 
@@ -157,10 +166,108 @@
         }
     }
 
+    /**
+     * Offsets the textViews of the clock for the step clock animation.
+     *
+     * The animation makes the textViews of the clock move at different speeds, when the clock is
+     * moving horizontally.
+     *
+     * @param clockStartLeft the [getLeft] position of the clock, before it started moving.
+     * @param clockMoveDirection the direction in which it is moving. A positive number means right,
+     *   and negative means left.
+     * @param moveFraction fraction of the clock movement. 0 means it is at the beginning, and 1
+     *   means it finished moving.
+     */
+    fun offsetGlyphsForStepClockAnimation(
+        clockStartLeft: Int,
+        clockMoveDirection: Int,
+        moveFraction: Float,
+    ) {
+        val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
+        // The sign of moveAmountDeltaForDigit is already set here
+        // we can interpret (left - clockStartLeft) as (destinationPosition - originPosition)
+        // so we no longer need to multiply direct sign to moveAmountDeltaForDigit
+        val currentMoveAmount = left - clockStartLeft
+        for (i in 0 until NUM_DIGITS) {
+            val mapIndexToId =
+                when (i) {
+                    0 -> R.id.HOUR_FIRST_DIGIT
+                    1 -> R.id.HOUR_SECOND_DIGIT
+                    2 -> R.id.MINUTE_FIRST_DIGIT
+                    3 -> R.id.MINUTE_SECOND_DIGIT
+                    else -> -1
+                }
+            val digitFraction =
+                getDigitFraction(
+                    digit = i,
+                    isMovingToCenter = isMovingToCenter,
+                    fraction = moveFraction,
+                )
+            // left here is the final left position after the animation is done
+            val moveAmountForDigit = currentMoveAmount * digitFraction
+            var moveAmountDeltaForDigit = moveAmountForDigit - currentMoveAmount
+            if (isMovingToCenter && moveAmountForDigit < 0) moveAmountDeltaForDigit *= -1
+            digitOffsets[mapIndexToId] = moveAmountDeltaForDigit
+            invalidate()
+        }
+    }
+
+    private val moveToCenterDelays: List<Int>
+        get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS
+
+    private val moveToSideDelays: List<Int>
+        get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS
+
+    private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float {
+        // The delay for the digit, in terms of fraction.
+        // (i.e. the digit should not move during 0.0 - 0.1).
+        val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays
+        val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP
+        return MOVE_INTERPOLATOR.getInterpolation(
+            constrainedMap(
+                /* rangeMin= */ 0.0f,
+                /* rangeMax= */ 1.0f,
+                /* valueMin= */ digitInitialDelay,
+                /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME,
+                /* value= */ fraction,
+            )
+        )
+    }
+
     companion object {
         val AOD_TRANSITION_DURATION = 750L
         val CHARGING_TRANSITION_DURATION = 300L
 
+        // Calculate the positions of all of the digits...
+        // Offset each digit by, say, 0.1
+        // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should
+        // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3
+        // from 0.3 - 1.0.
+        private const val NUM_DIGITS = 4
+
+        // Delays. Each digit's animation should have a slight delay, so we get a nice
+        // "stepping" effect. When moving right, the second digit of the hour should move first.
+        // When moving left, the first digit of the hour should move first. The lists encode
+        // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied
+        // by delayMultiplier.
+        private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3)
+        private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2)
+
+        // How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
+        // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
+        // before moving).
+        //
+        // The current specs dictate that each digit should have a 33ms gap between them. The
+        // overall time is 1s right now.
+        private const val MOVE_DIGIT_STEP = 0.033f
+
+        // Constants for the animation
+        private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
+
+        // Total available transition time for each digit, taking into account the step. If step is
+        // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
+        private const val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
+
         // Use the sign of targetTranslation to control the direction of digit translation
         fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
             val outPoint = Point(targetTranslation)
@@ -169,17 +276,14 @@
                     outPoint.x *= -1
                     outPoint.y *= -1
                 }
-
                 R.id.HOUR_SECOND_DIGIT -> {
                     outPoint.x *= 1
                     outPoint.y *= -1
                 }
-
                 R.id.MINUTE_FIRST_DIGIT -> {
                     outPoint.x *= -1
                     outPoint.y *= 1
                 }
-
                 R.id.MINUTE_SECOND_DIGIT -> {
                     outPoint.x *= 1
                     outPoint.y *= 1
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index e142169..58fe2c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -36,6 +36,7 @@
 
 private const val USER_ID = 22
 private const val OWNER_ID = 10
+private const val PASSWORD_ID = 30
 private const val OPERATION_ID = 100L
 private const val MAX_ATTEMPTS = 5
 
@@ -247,7 +248,11 @@
 private fun pinRequest(credentialOwner: Int = USER_ID): BiometricPromptRequest.Credential.Pin =
     BiometricPromptRequest.Credential.Pin(
         promptInfo(),
-        BiometricUserInfo(userId = USER_ID, deviceCredentialOwnerId = credentialOwner),
+        BiometricUserInfo(
+            userId = USER_ID,
+            deviceCredentialOwnerId = credentialOwner,
+            userIdForPasswordEntry = PASSWORD_ID,
+        ),
         BiometricOperationInfo(OPERATION_ID),
     )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 38ed22a..f0d79bb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeTrustRepository
 import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -40,6 +41,8 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -47,6 +50,7 @@
 import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -230,11 +234,7 @@
     @Test
     fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep() =
         testScope.runTest {
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
-                0,
-                kosmos.selectedUserInteractor.getSelectedUserId(),
-            )
+            setLockAfterScreenTimeout(0)
             kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false
             val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
 
@@ -254,11 +254,7 @@
     fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_afterDelay() =
         testScope.runTest {
             val delay = 5000
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
-                delay,
-                kosmos.selectedUserInteractor.getSelectedUserId(),
-            )
+            setLockAfterScreenTimeout(delay)
             kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false
             val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
 
@@ -279,11 +275,7 @@
     @Test
     fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_powerButtonLocksInstantly() =
         testScope.runTest {
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
-                5000,
-                kosmos.selectedUserInteractor.getSelectedUserId(),
-            )
+            setLockAfterScreenTimeout(5000)
             kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = true
             val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
 
@@ -304,11 +296,7 @@
     @Test
     fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleep() =
         testScope.runTest {
-            kosmos.fakeSettings.putIntForUser(
-                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
-                0,
-                kosmos.selectedUserInteractor.getSelectedUserId(),
-            )
+            setLockAfterScreenTimeout(0)
             val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
             assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
 
@@ -517,6 +505,98 @@
                 .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
         }
 
+    @Test
+    fun deviceUnlockStatus_locksImmediately_whenDreamStarts_noTimeout() =
+        testScope.runTest {
+            setLockAfterScreenTimeout(0)
+            val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked })
+            unlockDevice()
+
+            startDreaming()
+
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
+    fun deviceUnlockStatus_locksWithDelay_afterDreamStarts_withTimeout() =
+        testScope.runTest {
+            val delay = 5000
+            setLockAfterScreenTimeout(delay)
+            val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked })
+            unlockDevice()
+
+            startDreaming()
+            assertThat(isUnlocked).isTrue()
+
+            advanceTimeBy(delay - 1L)
+            assertThat(isUnlocked).isTrue()
+
+            advanceTimeBy(1L)
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
+    fun deviceUnlockStatus_doesNotLockWithDelay_whenDreamStopsBeforeTimeout() =
+        testScope.runTest {
+            val delay = 5000
+            setLockAfterScreenTimeout(delay)
+            val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked })
+            unlockDevice()
+
+            startDreaming()
+            assertThat(isUnlocked).isTrue()
+
+            advanceTimeBy(delay - 1L)
+            assertThat(isUnlocked).isTrue()
+
+            stopDreaming()
+            assertThat(isUnlocked).isTrue()
+
+            advanceTimeBy(1L)
+            assertThat(isUnlocked).isTrue()
+        }
+
+    @Test
+    fun deviceUnlockStatus_doesNotLock_whenDreamStarts_ifNotInteractive() =
+        testScope.runTest {
+            setLockAfterScreenTimeout(0)
+            val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked })
+            unlockDevice()
+
+            startDreaming()
+
+            assertThat(isUnlocked).isFalse()
+        }
+
+    private fun TestScope.unlockDevice() {
+        val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+        kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+            SuccessFingerprintAuthenticationStatus(0, true)
+        )
+        assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+        kosmos.sceneInteractor.changeScene(Scenes.Gone, "reason")
+        runCurrent()
+    }
+
+    private fun setLockAfterScreenTimeout(timeoutMs: Int) {
+        kosmos.fakeSettings.putIntForUser(
+            Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
+            timeoutMs,
+            kosmos.selectedUserInteractor.getSelectedUserId(),
+        )
+    }
+
+    private fun TestScope.startDreaming() {
+        kosmos.fakeKeyguardRepository.setDreaming(true)
+        runCurrent()
+    }
+
+    private fun TestScope.stopDreaming() {
+        kosmos.fakeKeyguardRepository.setDreaming(false)
+        runCurrent()
+    }
+
     private fun TestScope.verifyRestrictionReasonsForAuthFlags(
         vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
     ) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index bfe89de..3d5498b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -16,11 +16,14 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import android.animation.Animator
 import android.animation.ValueAnimator
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.app.animation.Interpolators
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -41,6 +44,8 @@
 import java.math.BigDecimal
 import java.math.RoundingMode
 import java.util.UUID
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.dropWhile
@@ -53,6 +58,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -65,6 +71,8 @@
     private lateinit var underTest: KeyguardTransitionRepository
     private lateinit var runner: KeyguardTransitionRunner
 
+    private val animatorListener = mock<Animator.AnimatorListener>()
+
     @Before
     fun setUp() {
         underTest = KeyguardTransitionRepositoryImpl(Dispatchers.Main)
@@ -80,7 +88,7 @@
             runner.startTransition(
                 this,
                 TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
-                maxFrames = 100
+                maxFrames = 100,
             )
 
             assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN)
@@ -107,7 +115,7 @@
                     LOCKSCREEN,
                     AOD,
                     getAnimator(),
-                    TransitionModeOnCanceled.LAST_VALUE
+                    TransitionModeOnCanceled.LAST_VALUE,
                 ),
             )
 
@@ -142,7 +150,7 @@
                     LOCKSCREEN,
                     AOD,
                     getAnimator(),
-                    TransitionModeOnCanceled.RESET
+                    TransitionModeOnCanceled.RESET,
                 ),
             )
 
@@ -177,7 +185,7 @@
                     LOCKSCREEN,
                     AOD,
                     getAnimator(),
-                    TransitionModeOnCanceled.REVERSE
+                    TransitionModeOnCanceled.REVERSE,
                 ),
             )
 
@@ -476,6 +484,49 @@
             assertThat(steps.size).isEqualTo(3)
         }
 
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF)
+    fun forceFinishCurrentTransition_noFurtherStepsEmitted() =
+        testScope.runTest {
+            val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF })
+
+            var sentForceFinish = false
+
+            runner.startTransition(
+                this,
+                TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+                maxFrames = 100,
+                // Force-finish on the second frame.
+                frameCallback = { frameNumber ->
+                    if (!sentForceFinish && frameNumber > 1) {
+                        testScope.launch { underTest.forceFinishCurrentTransition() }
+                        sentForceFinish = true
+                    }
+                },
+            )
+
+            val lastTwoRunningSteps =
+                steps.filter { it.transitionState == TransitionState.RUNNING }.takeLast(2)
+
+            // Make sure we stopped emitting RUNNING steps early, but then emitted a final 1f step.
+            assertTrue(lastTwoRunningSteps[0].value < 0.5f)
+            assertTrue(lastTwoRunningSteps[1].value == 1f)
+
+            assertEquals(steps.last().from, AOD)
+            assertEquals(steps.last().to, LOCKSCREEN)
+            assertEquals(steps.last().transitionState, TransitionState.FINISHED)
+        }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_TRANSITION_FORCE_FINISH_ON_SCREEN_OFF)
+    fun forceFinishCurrentTransition_noTransitionStarted_noStepsEmitted() =
+        testScope.runTest {
+            val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF })
+
+            underTest.forceFinishCurrentTransition()
+            assertEquals(0, steps.size)
+        }
+
     private fun listWithStep(
         step: BigDecimal,
         start: BigDecimal = BigDecimal.ZERO,
@@ -505,7 +556,7 @@
                     to,
                     fractions[0].toFloat(),
                     TransitionState.STARTED,
-                    OWNER_NAME
+                    OWNER_NAME,
                 )
             )
         fractions.forEachIndexed { index, fraction ->
@@ -519,7 +570,7 @@
                         to,
                         fraction.toFloat(),
                         TransitionState.RUNNING,
-                        OWNER_NAME
+                        OWNER_NAME,
                     )
                 )
         }
@@ -538,6 +589,7 @@
         return ValueAnimator().apply {
             setInterpolator(Interpolators.LINEAR)
             setDuration(10)
+            addListener(animatorListener)
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index ecc62e9..87ab3c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -69,7 +69,9 @@
         get() =
             kosmos.fakeSystemBarUtilsProxy.getStatusBarHeight() +
                 context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
-                context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+                context.resources.getDimensionPixelSize(
+                    customR.dimen.keyguard_smartspace_top_offset
+                )
 
     private val LARGE_CLOCK_TOP
         get() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index 1abb441..5798e07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -21,6 +21,7 @@
 import android.view.Choreographer.FrameCallback
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import java.util.function.Consumer
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
@@ -35,9 +36,8 @@
  * Gives direct control over ValueAnimator, in order to make transition tests deterministic. See
  * [AnimationHandler]. Animators are required to be run on the main thread, so dispatch accordingly.
  */
-class KeyguardTransitionRunner(
-    val repository: KeyguardTransitionRepository,
-) : AnimationFrameCallbackProvider {
+class KeyguardTransitionRunner(val repository: KeyguardTransitionRepository) :
+    AnimationFrameCallbackProvider {
 
     private var frameCount = 1L
     private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
@@ -48,7 +48,12 @@
      * For transitions being directed by an animator. Will control the number of frames being
      * generated so the values are deterministic.
      */
-    suspend fun startTransition(scope: CoroutineScope, info: TransitionInfo, maxFrames: Int = 100) {
+    suspend fun startTransition(
+        scope: CoroutineScope,
+        info: TransitionInfo,
+        maxFrames: Int = 100,
+        frameCallback: Consumer<Long>? = null,
+    ) {
         // AnimationHandler uses ThreadLocal storage, and ValueAnimators MUST start from main
         // thread
         withContext(Dispatchers.Main) {
@@ -62,7 +67,12 @@
 
                     isTerminated = frameNumber >= maxFrames
                     if (!isTerminated) {
-                        withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) }
+                        try {
+                            withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) }
+                            frameCallback?.accept(frameNumber)
+                        } catch (e: IllegalStateException) {
+                            e.printStackTrace()
+                        }
                     }
                 }
             }
@@ -90,9 +100,13 @@
     override fun postFrameCallback(cb: FrameCallback) {
         frames.value = Pair(frameCount++, cb)
     }
+
     override fun postCommitCallback(runnable: Runnable) {}
+
     override fun getFrameTime() = frameCount
+
     override fun getFrameDelay() = 1L
+
     override fun setFrameDelay(delay: Long) {}
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index 3910903..ae7c44e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -35,7 +35,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class EditTileListStateTest : SysuiTestCase() {
-    private val underTest = EditTileListState(TestEditTiles, 4)
+    private val underTest = EditTileListState(TestEditTiles, columns = 4, largeTilesSpan = 2)
 
     @Test
     fun startDrag_listHasSpacers() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index ea5c29e..3ad41a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
+import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertFalse;
@@ -32,9 +34,9 @@
 import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
 
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.testing.TestableLooper;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.compose.animation.scene.ObservableTransitionState;
@@ -42,6 +44,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.BrokenWithSceneContainer;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -78,14 +81,23 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.verification.VerificationMode;
 
+import java.util.List;
+
 import kotlinx.coroutines.flow.MutableStateFlow;
 import kotlinx.coroutines.test.TestScope;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper
 public class VisualStabilityCoordinatorTest extends SysuiTestCase {
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return parameterizeSceneContainerFlag();
+    }
+
     private VisualStabilityCoordinator mCoordinator;
 
     @Mock private DumpManager mDumpManager;
@@ -117,6 +129,11 @@
     private NotificationEntry mEntry;
     private GroupEntry mGroupEntry;
 
+    public VisualStabilityCoordinatorTest(FlagsParameterization flags) {
+        super();
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -251,6 +268,7 @@
     }
 
     @Test
+    @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
     public void testLockscreenPartlyShowing_groupAndSectionChangesNotAllowed() {
         // GIVEN the panel true expanded and device isn't pulsing
         setFullyDozed(false);
@@ -267,6 +285,7 @@
     }
 
     @Test
+    @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
     public void testLockscreenFullyShowing_groupAndSectionChangesNotAllowed() {
         // GIVEN the panel true expanded and device isn't pulsing
         setFullyDozed(false);
@@ -520,6 +539,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_CHECK_LOCKSCREEN_GONE_TRANSITION)
+    @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
     public void testNotLockscreenInGoneTransition_invalidationCalled() {
         // GIVEN visual stability is being maintained b/c animation is playing
         mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava(
@@ -589,6 +609,7 @@
     }
 
     @Test
+    @BrokenWithSceneContainer(bugId = 377868472) // mReorderingAllowed is broken with SceneContainer
     public void testCommunalShowingWillNotSuppressReordering() {
         // GIVEN panel is expanded, communal is showing, and QS is collapsed
         setPulsing(false);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 7d55169..89da465 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -13,11 +13,20 @@
  */
 package com.android.systemui.plugins.clocks
 
+import android.content.Context
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
+import android.util.DisplayMetrics
 import android.view.View
 import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
 import com.android.internal.annotations.Keep
+import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.plugins.Plugin
 import com.android.systemui.plugins.annotations.GeneratedImport
@@ -149,7 +158,7 @@
 
     @ProtectedReturn("return constraints;")
     /** Custom constraints to apply to preview ConstraintLayout. */
-    fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
+    fun applyPreviewConstraints(context: Context, constraints: ConstraintSet): ConstraintSet
 
     fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
 }
@@ -169,13 +178,84 @@
         return constraints
     }
 
-    override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet {
-        return constraints
+    override fun applyPreviewConstraints(
+        context: Context,
+        constraints: ConstraintSet,
+    ): ConstraintSet {
+        return applyDefaultPreviewConstraints(context, constraints)
     }
 
     override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) {
         // Default clock doesn't need detailed control of view
     }
+
+    companion object {
+        fun applyDefaultPreviewConstraints(
+            context: Context,
+            constraints: ConstraintSet,
+        ): ConstraintSet {
+            constraints.apply {
+                val lockscreenClockViewLargeId = getId(context, "lockscreen_clock_view_large")
+                constrainWidth(lockscreenClockViewLargeId, WRAP_CONTENT)
+                constrainHeight(lockscreenClockViewLargeId, WRAP_CONTENT)
+                constrainMaxHeight(lockscreenClockViewLargeId, 0)
+
+                val largeClockTopMargin =
+                    SystemBarUtils.getStatusBarHeight(context) +
+                        getDimen(context, "small_clock_padding_top") +
+                        getDimen(context, "keyguard_smartspace_top_offset") +
+                        getDimen(context, "date_weather_view_height") +
+                        getDimen(context, "enhanced_smartspace_height")
+                connect(lockscreenClockViewLargeId, TOP, PARENT_ID, TOP, largeClockTopMargin)
+                connect(lockscreenClockViewLargeId, START, PARENT_ID, START)
+                connect(lockscreenClockViewLargeId, END, PARENT_ID, END)
+
+                // In preview, we'll show UDFPS icon for UDFPS devices
+                // and nothing for non-UDFPS devices,
+                // and we're not planning to add this vide in clockHostView
+                // so we only need position of device entry icon to constrain clock
+                // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
+                val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
+                val defaultDensity =
+                    DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+                        DisplayMetrics.DENSITY_DEFAULT.toFloat()
+                val lockIconRadiusPx = (defaultDensity * 36).toInt()
+                val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
+
+                connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin)
+                val smallClockViewId = getId(context, "lockscreen_clock_view")
+                constrainWidth(smallClockViewId, WRAP_CONTENT)
+                constrainHeight(smallClockViewId, getDimen(context, "small_clock_height"))
+                connect(
+                    smallClockViewId,
+                    START,
+                    PARENT_ID,
+                    START,
+                    getDimen(context, "clock_padding_start") +
+                        getDimen(context, "status_view_margin_horizontal"),
+                )
+                val smallClockTopMargin =
+                    getDimen(context, "keyguard_clock_top_margin") +
+                        SystemBarUtils.getStatusBarHeight(context)
+                connect(smallClockViewId, TOP, PARENT_ID, TOP, smallClockTopMargin)
+            }
+            return constraints
+        }
+
+        fun getId(context: Context, name: String): Int {
+            val packageName = context.packageName
+            val res = context.packageManager.getResourcesForApplication(packageName)
+            val id = res.getIdentifier(name, "id", packageName)
+            return id
+        }
+
+        fun getDimen(context: Context, name: String): Int {
+            val packageName = context.packageName
+            val res = context.packageManager.getResourcesForApplication(packageName)
+            val id = res.getIdentifier(name, "dimen", packageName)
+            return if (id == 0) 0 else res.getDimensionPixelSize(id)
+        }
+    }
 }
 
 /** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 2a27b47..4a53df9 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -24,7 +24,6 @@
     <!-- margin from keyguard status bar to clock. For split shade it should be
          keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
     <dimen name="keyguard_clock_top_margin">8dp</dimen>
-    <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
 
     <!-- QS-->
     <dimen name="qs_panel_padding_top">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml
index f556b97..53d921b 100644
--- a/packages/SystemUI/res/values-sw600dp-port/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/config.xml
@@ -33,6 +33,9 @@
     <!-- The number of columns in the infinite grid QuickSettings -->
     <integer name="quick_settings_infinite_grid_num_columns">6</integer>
 
+    <!-- The maximum width of large tiles in the infinite grid QuickSettings -->
+    <integer name="quick_settings_infinite_grid_tile_max_width">3</integer>
+
     <integer name="power_menu_lite_max_columns">2</integer>
     <integer name="power_menu_lite_max_rows">3</integer>
 
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 393631e..26f32ef 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -126,6 +126,4 @@
     <dimen name="controls_content_padding">24dp</dimen>
     <dimen name="control_list_vertical_spacing">8dp</dimen>
     <dimen name="control_list_horizontal_spacing">16dp</dimen>
-    <!-- For portrait direction in unfold foldable device, we don't need keyguard_smartspace_top_offset-->
-    <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 0854eb4..48af82a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -79,6 +79,9 @@
     <!-- The number of columns in the infinite grid QuickSettings -->
     <integer name="quick_settings_infinite_grid_num_columns">4</integer>
 
+    <!-- The maximum width of large tiles in the infinite grid QuickSettings -->
+    <integer name="quick_settings_infinite_grid_tile_max_width">4</integer>
+
     <!-- The number of columns in the Dual Shade QuickSettings -->
     <integer name="quick_settings_dual_shade_num_columns">4</integer>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7fa2879..67eb5b0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -815,8 +815,7 @@
     <dimen name="keyguard_clock_top_margin">18dp</dimen>
     <!-- The amount to shift the clocks during a small/large transition -->
     <dimen name="keyguard_clock_switch_y_shift">14dp</dimen>
-    <!-- When large clock is showing, offset the smartspace by this amount -->
-    <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
+
     <!-- The amount to translate lockscreen elements on the GONE->AOD transition -->
     <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>
     <!-- The amount to translate lockscreen elements on the GONE->AOD transition, on device fold -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 11dde6a..71d4e9a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -147,7 +147,7 @@
         mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
                 R.dimen.keyguard_clock_switch_y_shift);
         mSmartspaceTopOffset = (int) (mContext.getResources().getDimensionPixelSize(
-                        R.dimen.keyguard_smartspace_top_offset)
+                com.android.systemui.customization.R.dimen.keyguard_smartspace_top_offset)
                 * mContext.getResources().getConfiguration().fontScale
                 / mContext.getResources().getDisplayMetrics().density
                 * SMARTSPACE_TOP_PADDING_MULTIPLIER);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 811b47d..a46b236 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.animation.Animator;
 import android.annotation.SuppressLint;
 import android.app.ActivityThread;
 import android.app.Application;
@@ -135,6 +136,9 @@
         if (Flags.enableLayoutTracing()) {
             View.setTraceLayoutSteps(true);
         }
+        if (com.android.window.flags.Flags.systemUiPostAnimationEnd()) {
+            Animator.setPostNotifyEndListenerEnabled(true);
+        }
 
         if (mProcessWrapper.isSystemUser()) {
             IntentFilter bootCompletedFilter = new
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index b070068..08b3e99 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -72,7 +72,7 @@
         // Request LockSettingsService to return the Gatekeeper Password in the
         // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
         // Gatekeeper Password and operationId.
-        var effectiveUserId = request.userInfo.userIdForPasswordEntry
+        var effectiveUserId = request.userInfo.deviceCredentialOwnerId
         val response =
             if (Flags.privateSpaceBp() && effectiveUserId != request.userInfo.userId) {
                 effectiveUserId = request.userInfo.userId
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 67584a5..b74ca03 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -30,11 +30,13 @@
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
 import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.TrustInteractor
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
@@ -49,6 +51,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
@@ -69,6 +72,7 @@
     private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
     private val systemPropertiesHelper: SystemPropertiesHelper,
     private val userAwareSecureSettingsRepository: UserAwareSecureSettingsRepository,
+    private val keyguardInteractor: KeyguardInteractor,
 ) : ExclusiveActivatable() {
 
     private val deviceUnlockSource =
@@ -236,6 +240,21 @@
                     .distinctUntilChanged()
                     .filter { it }
                     .map { LockImmediately("lockdown") },
+                // Started dreaming
+                powerInteractor.isInteractive.flatMapLatestConflated { isInteractive ->
+                    // Only respond to dream state changes while the device is interactive.
+                    if (isInteractive) {
+                        keyguardInteractor.isDreamingAny.distinctUntilChanged().map { isDreaming ->
+                            if (isDreaming) {
+                                LockWithDelay("started dreaming")
+                            } else {
+                                CancelDelayedLock("stopped dreaming")
+                            }
+                        }
+                    } else {
+                        emptyFlow()
+                    }
+                },
             )
             .collectLatest(::onLockEvent)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index dd08d32..7a95a41 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -40,7 +40,6 @@
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Flags;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dock.DockManager;
@@ -566,8 +565,7 @@
         }
 
         // When already in pulsing, we can show the new Notification without requesting a new pulse.
-        if (Flags.notificationPulsingFix()
-                && dozeState == State.DOZE_PULSING && reason == DozeLog.PULSE_REASON_NOTIFICATION) {
+        if (dozeState == State.DOZE_PULSING && reason == DozeLog.PULSE_REASON_NOTIFICATION) {
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 3a5614f..eaf8fa9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -114,6 +114,18 @@
         @FloatRange(from = 0.0, to = 1.0) value: Float,
         state: TransitionState,
     )
+
+    /**
+     * Forces the current transition to emit FINISHED, foregoing any additional RUNNING steps that
+     * otherwise would have been emitted.
+     *
+     * When the screen is off, upcoming performance changes cause all Animators to cease emitting
+     * frames, which means the Animator passed to [startTransition] will never finish if it was
+     * running when the screen turned off. Also, there's simply no reason to emit RUNNING steps when
+     * the screen isn't even on. As long as we emit FINISHED, everything should end up in the
+     * correct state.
+     */
+    suspend fun forceFinishCurrentTransition()
 }
 
 @SysUISingleton
@@ -134,6 +146,7 @@
     override val transitions = _transitions.asSharedFlow().distinctUntilChanged()
     private var lastStep: TransitionStep = TransitionStep()
     private var lastAnimator: ValueAnimator? = null
+    private var animatorListener: AnimatorListenerAdapter? = null
 
     private val withContextMutex = Mutex()
     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
@@ -233,7 +246,7 @@
                     )
                 }
 
-                val adapter =
+                animatorListener =
                     object : AnimatorListenerAdapter() {
                         override fun onAnimationStart(animation: Animator) {
                             emitTransition(
@@ -254,9 +267,10 @@
                             animator.removeListener(this)
                             animator.removeUpdateListener(updateListener)
                             lastAnimator = null
+                            animatorListener = null
                         }
                     }
-                animator.addListener(adapter)
+                animator.addListener(animatorListener)
                 animator.addUpdateListener(updateListener)
                 animator.start()
                 return@withContext null
@@ -290,6 +304,33 @@
         }
     }
 
+    override suspend fun forceFinishCurrentTransition() {
+        withContextMutex.lock()
+
+        if (lastAnimator?.isRunning != true) {
+            return
+        }
+
+        return withContext("$TAG#forceFinishCurrentTransition", mainDispatcher) {
+            withContextMutex.unlock()
+
+            Log.d(TAG, "forceFinishCurrentTransition() - emitting FINISHED early.")
+
+            lastAnimator?.apply {
+                // Cancel the animator, but remove listeners first so we don't emit CANCELED.
+                removeAllListeners()
+                cancel()
+
+                // Emit a final 1f RUNNING step to ensure that any transitions not listening for a
+                // FINISHED step end up in the right end state.
+                emitTransition(TransitionStep(currentTransitionInfo, 1f, TransitionState.RUNNING))
+
+                // Ask the listener to emit FINISHED and clean up its state.
+                animatorListener?.onAnimationEnd(this)
+            }
+        }
+    }
+
     private suspend fun updateTransitionInternal(
         transitionId: UUID,
         @FloatRange(from = 0.0, to = 1.0) value: Float,
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 b815f19..7cd2744 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,8 +19,10 @@
 
 import android.annotation.SuppressLint
 import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags.keyguardTransitionForceFinishOnScreenOff
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -30,6 +32,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
@@ -59,7 +63,6 @@
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Encapsulates business-logic related to the keyguard transitions. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -70,6 +73,7 @@
     @Application val scope: CoroutineScope,
     private val repository: KeyguardTransitionRepository,
     private val sceneInteractor: SceneInteractor,
+    private val powerInteractor: PowerInteractor,
 ) {
     private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>()
 
@@ -188,6 +192,18 @@
                     }
                 }
         }
+
+        if (keyguardTransitionForceFinishOnScreenOff()) {
+            /**
+             * If the screen is turning off, finish the current transition immediately. Further
+             * frames won't be visible anyway.
+             */
+            scope.launch {
+                powerInteractor.screenPowerState
+                    .filter { it == ScreenPowerState.SCREEN_TURNING_OFF }
+                    .collect { repository.forceFinishCurrentTransition() }
+            }
+        }
     }
 
     fun transition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<TransitionStep> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 46f5c05..914fdd2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -18,34 +18,23 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.content.Context
-import android.util.DisplayMetrics
 import android.view.View
 import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.internal.policy.SystemBarUtils
-import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
-import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
 import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
 import com.android.systemui.shared.clocks.ClockRegistry
-import com.android.systemui.util.Utils
 import kotlin.reflect.KSuspendFunction1
 
 /** Binder for the small clock view, large clock view. */
@@ -131,78 +120,6 @@
         }
     }
 
-    private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
-        constraints.apply {
-            constrainWidth(customR.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
-            // The following two lines make lockscreen_clock_view_large is constrained to available
-            // height when it goes beyond constraints; otherwise, it use WRAP_CONTENT
-            constrainHeight(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
-            constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0)
-            val largeClockTopMargin =
-                SystemBarUtils.getStatusBarHeight(context) +
-                    context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
-                    context.resources.getDimensionPixelSize(
-                        R.dimen.keyguard_smartspace_top_offset
-                    ) +
-                    getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
-                    getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
-            connect(
-                customR.id.lockscreen_clock_view_large,
-                TOP,
-                PARENT_ID,
-                TOP,
-                largeClockTopMargin,
-            )
-            connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
-            connect(
-                customR.id.lockscreen_clock_view_large,
-                ConstraintSet.END,
-                PARENT_ID,
-                ConstraintSet.END,
-            )
-
-            // In preview, we'll show UDFPS icon for UDFPS devices and nothing for non-UDFPS
-            // devices, but we need position of device entry icon to constrain clock
-            if (getConstraint(lockId) != null) {
-                connect(customR.id.lockscreen_clock_view_large, BOTTOM, lockId, TOP)
-            } else {
-                // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
-                val bottomPaddingPx =
-                    context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
-                val defaultDensity =
-                    DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
-                        DisplayMetrics.DENSITY_DEFAULT.toFloat()
-                val lockIconRadiusPx = (defaultDensity * 36).toInt()
-                val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
-                connect(
-                    customR.id.lockscreen_clock_view_large,
-                    BOTTOM,
-                    PARENT_ID,
-                    BOTTOM,
-                    clockBottomMargin,
-                )
-            }
-
-            constrainWidth(customR.id.lockscreen_clock_view, WRAP_CONTENT)
-            constrainHeight(
-                customR.id.lockscreen_clock_view,
-                context.resources.getDimensionPixelSize(customR.dimen.small_clock_height),
-            )
-            connect(
-                customR.id.lockscreen_clock_view,
-                START,
-                PARENT_ID,
-                START,
-                context.resources.getDimensionPixelSize(customR.dimen.clock_padding_start) +
-                    context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal),
-            )
-            val smallClockTopMargin =
-                context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
-                    Utils.getStatusBarHeaderHeightKeyguard(context)
-            connect(customR.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
-        }
-    }
-
     private fun applyPreviewConstraints(
         context: Context,
         rootView: ConstraintLayout,
@@ -210,9 +127,8 @@
         viewModel: KeyguardPreviewClockViewModel,
     ) {
         val cs = ConstraintSet().apply { clone(rootView) }
-        applyClockDefaultConstraints(context, cs)
-        previewClock.largeClock.layout.applyPreviewConstraints(cs)
-        previewClock.smallClock.layout.applyPreviewConstraints(cs)
+        previewClock.largeClock.layout.applyPreviewConstraints(context, cs)
+        previewClock.smallClock.layout.applyPreviewConstraints(context, cs)
 
         // When selectedClockSize is the initial value, make both clocks invisible to avoid
         // flickering
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index ee4f41d..6096cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -186,12 +186,23 @@
         constraints.apply {
             connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
             connect(customR.id.lockscreen_clock_view_large, END, guideline, END)
-            connect(customR.id.lockscreen_clock_view_large, BOTTOM, R.id.device_entry_icon_view, TOP)
+            connect(
+                customR.id.lockscreen_clock_view_large,
+                BOTTOM,
+                R.id.device_entry_icon_view,
+                TOP,
+            )
             val largeClockTopMargin =
                 keyguardClockViewModel.getLargeClockTopMargin() +
                     getDimen(DATE_WEATHER_VIEW_HEIGHT) +
                     getDimen(ENHANCED_SMARTSPACE_HEIGHT)
-            connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+            connect(
+                customR.id.lockscreen_clock_view_large,
+                TOP,
+                PARENT_ID,
+                TOP,
+                largeClockTopMargin,
+            )
             constrainWidth(customR.id.lockscreen_clock_view_large, WRAP_CONTENT)
 
             // The following two lines make lockscreen_clock_view_large is constrained to available
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 5c79c0b..82adced 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -181,7 +181,7 @@
     fun getLargeClockTopMargin(): Int {
         return systemBarUtils.getStatusBarHeight() +
             resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) +
-            resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+            resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset)
     }
 
     val largeClockTopMargin: Flow<Int> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index 6579ea1..65c0f57 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.customization.R as customR
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.ClockSizeSetting
 import com.android.systemui.res.R
@@ -39,20 +40,16 @@
     val selectedClockSize: StateFlow<ClockSizeSetting> = interactor.selectedClockSize
 
     val shouldHideSmartspace: Flow<Boolean> =
-        combine(
-                interactor.selectedClockSize,
-                interactor.currentClockId,
-                ::Pair,
-            )
-            .map { (size, currentClockId) ->
-                when (size) {
-                    // TODO (b/284122375) This is temporary. We should use clockController
-                    //      .largeClock.config.hasCustomWeatherDataDisplay instead, but
-                    //      ClockRegistry.createCurrentClock is not reliable.
-                    ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
-                    ClockSizeSetting.SMALL -> false
-                }
+        combine(interactor.selectedClockSize, interactor.currentClockId, ::Pair).map {
+            (size, currentClockId) ->
+            when (size) {
+                // TODO (b/284122375) This is temporary. We should use clockController
+                //      .largeClock.config.hasCustomWeatherDataDisplay instead, but
+                //      ClockRegistry.createCurrentClock is not reliable.
+                ClockSizeSetting.DYNAMIC -> currentClockId == "DIGITAL_CLOCK_WEATHER"
+                ClockSizeSetting.SMALL -> false
             }
+        }
 
     fun getSmartspaceStartPadding(context: Context): Int {
         return KeyguardSmartspaceViewModel.getSmartspaceStartMargin(context)
@@ -83,7 +80,7 @@
             } else {
                 getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
                     SystemBarUtils.getStatusBarHeight(context) +
-                    getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+                    getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 850e943..ef6ae0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.content.res.Resources
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.customization.R as customR
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -42,7 +44,6 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 class LockscreenContentViewModel
 @AssistedInject
@@ -82,10 +83,7 @@
                         unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
                         unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
                     ) { start, end ->
-                        UnfoldTranslations(
-                            start = start,
-                            end = end,
-                        )
+                        UnfoldTranslations(start = start, end = end)
                     }
                     .collect { _unfoldTranslations.value = it }
             }
@@ -102,17 +100,15 @@
 
     /** Returns a flow that indicates whether lockscreen notifications should be rendered. */
     fun areNotificationsVisible(): Flow<Boolean> {
-        return combine(
-            clockSize,
-            shadeInteractor.isShadeLayoutWide,
-        ) { clockSize, isShadeLayoutWide ->
+        return combine(clockSize, shadeInteractor.isShadeLayoutWide) { clockSize, isShadeLayoutWide
+            ->
             clockSize == ClockSize.SMALL || isShadeLayoutWide
         }
     }
 
     fun getSmartSpacePaddingTop(resources: Resources): Int {
         return if (clockSize.value == ClockSize.LARGE) {
-            resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
+            resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset) +
                 resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
         } else {
             0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
new file mode 100644
index 0000000..5883403
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepository.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.emitOnStart
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class LargeTileSpanRepository
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    @Main private val resources: Resources,
+    configurationRepository: ConfigurationRepository,
+) {
+    val span: StateFlow<Int> =
+        configurationRepository.onConfigurationChange
+            .emitOnStart()
+            .mapLatest {
+                if (resources.configuration.fontScale >= FONT_SCALE_THRESHOLD) {
+                    resources.getInteger(R.integer.quick_settings_infinite_grid_tile_max_width)
+                } else {
+                    2
+                }
+            }
+            .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.WhileSubscribed(), 2)
+
+    private companion object {
+        const val FONT_SCALE_THRESHOLD = 2f
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index ec61a0d..23c79f5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -21,12 +21,14 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.LargeTileSpanRepository
 import com.android.systemui.qs.panels.shared.model.PanelsLog
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
@@ -38,6 +40,7 @@
     private val repo: DefaultLargeTilesRepository,
     private val currentTilesInteractor: CurrentTilesInteractor,
     private val preferencesInteractor: QSPreferencesInteractor,
+    largeTilesSpanRepo: LargeTileSpanRepository,
     @PanelsLog private val logBuffer: LogBuffer,
     @Application private val applicationScope: CoroutineScope,
 ) {
@@ -46,6 +49,8 @@
             .onEach { logChange(it) }
             .stateIn(applicationScope, SharingStarted.Eagerly, repo.defaultLargeTiles)
 
+    val largeTilesSpan: StateFlow<Int> = largeTilesSpanRepo.span
+
     fun isIconTile(spec: TileSpec): Boolean = !largeTilesSpecs.value.contains(spec)
 
     fun setLargeTiles(specs: Set<TileSpec>) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index 74fa0fe..c729c7c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -37,13 +37,17 @@
 fun rememberEditListState(
     tiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
+    largeTilesSpan: Int,
 ): EditTileListState {
-    return remember(tiles, columns) { EditTileListState(tiles, columns) }
+    return remember(tiles, columns) { EditTileListState(tiles, columns, largeTilesSpan) }
 }
 
 /** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
-class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>, private val columns: Int) :
-    DragAndDropState {
+class EditTileListState(
+    tiles: List<SizedTile<EditTileViewModel>>,
+    private val columns: Int,
+    private val largeTilesSpan: Int,
+) : DragAndDropState {
     private val _draggedCell = mutableStateOf<SizedTile<EditTileViewModel>?>(null)
     override val draggedCell
         get() = _draggedCell.value
@@ -86,7 +90,7 @@
         if (fromIndex != -1) {
             val cell = _tiles.removeAt(fromIndex)
             cell as TileGridCell
-            _tiles.add(fromIndex, cell.copy(width = if (cell.isIcon) 2 else 1))
+            _tiles.add(fromIndex, cell.copy(width = if (cell.isIcon) largeTilesSpan else 1))
             regenerateGrid(fromIndex)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index b5cec12..31ea60e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -26,7 +26,7 @@
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.LocalOverscrollConfiguration
+import androidx.compose.foundation.LocalOverscrollFactory
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.Orientation
@@ -173,6 +173,7 @@
     listState: EditTileListState,
     otherTiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
+    largeTilesSpan: Int,
     modifier: Modifier,
     onRemoveTile: (TileSpec) -> Unit,
     onSetTiles: (List<TileSpec>) -> Unit,
@@ -203,7 +204,7 @@
         containerColor = Color.Transparent,
         topBar = { EditModeTopBar(onStopEditing = onStopEditing, onReset = reset) },
     ) { innerPadding ->
-        CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
+        CompositionLocalProvider(LocalOverscrollFactory provides null) {
             val scrollState = rememberScrollState()
             LaunchedEffect(listState.dragInProgress) {
                 if (listState.dragInProgress) {
@@ -230,7 +231,14 @@
                     }
                 }
 
-                CurrentTilesGrid(listState, selectionState, columns, onResize, onSetTiles)
+                CurrentTilesGrid(
+                    listState,
+                    selectionState,
+                    columns,
+                    largeTilesSpan,
+                    onResize,
+                    onSetTiles,
+                )
 
                 // Hide available tiles when dragging
                 AnimatedVisibility(
@@ -273,7 +281,7 @@
     ) {
         Box(
             contentAlignment = Alignment.Center,
-            modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight),
+            modifier = modifier.fillMaxWidth().wrapContentHeight(),
         ) {
             content()
         }
@@ -300,6 +308,7 @@
     listState: EditTileListState,
     selectionState: MutableSelectionState,
     columns: Int,
+    largeTilesSpan: Int,
     onResize: (TileSpec, toIcon: Boolean) -> Unit,
     onSetTiles: (List<TileSpec>) -> Unit,
 ) {
@@ -340,7 +349,8 @@
                 }
                 .testTag(CURRENT_TILES_GRID_TEST_TAG),
     ) {
-        EditTiles(cells, columns, listState, selectionState, coroutineScope) { spec ->
+        EditTiles(cells, columns, listState, selectionState, coroutineScope, largeTilesSpan) { spec
+            ->
             // Toggle the current size of the tile
             currentListState.isIcon(spec)?.let { onResize(spec, !it) }
         }
@@ -425,6 +435,7 @@
     dragAndDropState: DragAndDropState,
     selectionState: MutableSelectionState,
     coroutineScope: CoroutineScope,
+    largeTilesSpan: Int,
     onToggleSize: (spec: TileSpec) -> Unit,
 ) {
     items(
@@ -456,6 +467,7 @@
                         onToggleSize = onToggleSize,
                         coroutineScope = coroutineScope,
                         bounceableInfo = cells.bounceableInfo(index, columns),
+                        largeTilesSpan = largeTilesSpan,
                         modifier = Modifier.animateItem(),
                     )
                 }
@@ -472,6 +484,7 @@
     selectionState: MutableSelectionState,
     onToggleSize: (spec: TileSpec) -> Unit,
     coroutineScope: CoroutineScope,
+    largeTilesSpan: Int,
     bounceableInfo: BounceableInfo,
     modifier: Modifier = Modifier,
 ) {
@@ -514,8 +527,11 @@
                 .fillMaxWidth()
                 .onSizeChanged {
                     // Grab the size before the bounceable to get the idle width
-                    val min = if (cell.isIcon) it.width else (it.width - padding) / 2
-                    val max = if (cell.isIcon) (it.width * 2) + padding else it.width
+                    val totalPadding = (largeTilesSpan - 1) * padding
+                    val min =
+                        if (cell.isIcon) it.width else (it.width - totalPadding) / largeTilesSpan
+                    val max =
+                        if (cell.isIcon) (it.width * largeTilesSpan) + totalPadding else it.width
                     tileWidths = TileWidths(it.width, min, max)
                 }
                 .bounceable(
@@ -554,15 +570,13 @@
             val targetValue = if (cell.isIcon) 0f else 1f
             val animatedProgress = remember { Animatable(targetValue) }
 
-            if (selected) {
-                val resizingState = selectionState.resizingState
-                LaunchedEffect(targetValue, resizingState) {
-                    if (resizingState == null) {
-                        animatedProgress.animateTo(targetValue)
-                    } else {
-                        snapshotFlow { resizingState.progression }
-                            .collectLatest { animatedProgress.snapTo(it) }
-                    }
+            val resizingState = selectionState.resizingState?.takeIf { selected }
+            LaunchedEffect(targetValue, resizingState) {
+                if (resizingState == null) {
+                    animatedProgress.animateTo(targetValue)
+                } else {
+                    snapshotFlow { resizingState.progression }
+                        .collectLatest { animatedProgress.snapTo(it) }
                 }
             }
 
@@ -705,7 +719,6 @@
 
 private object EditModeTileDefaults {
     const val PLACEHOLDER_ALPHA = .3f
-    val EditGridHeaderHeight = 60.dp
     val CurrentTilesGridPadding = 8.dp
 
     @Composable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 5ac2ad0..29ff171 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -79,7 +79,8 @@
             }
 
         val columns = columnsWithMediaViewModel.columns
-        val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
+        val largeTilesSpan by iconTilesViewModel.largeTilesSpanState
+        val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width(largeTilesSpan)) }
         val bounceables =
             remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
         val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
@@ -129,21 +130,23 @@
                 viewModel.columnsWithMediaViewModelFactory.createWithoutMediaTracking()
             }
         val columns = columnsViewModel.columns
+        val largeTilesSpan by iconTilesViewModel.largeTilesSpanState
         val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
 
         // Non-current tiles should always be displayed as icon tiles.
         val sizedTiles =
-            remember(tiles, largeTiles) {
+            remember(tiles, largeTiles, largeTilesSpan) {
                 tiles.map {
                     SizedTileImpl(
                         it,
-                        if (!it.isCurrent || !largeTiles.contains(it.tileSpec)) 1 else 2,
+                        if (!it.isCurrent || !largeTiles.contains(it.tileSpec)) 1
+                        else largeTilesSpan,
                     )
                 }
             }
 
         val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
-        val currentListState = rememberEditListState(currentTiles, columns)
+        val currentListState = rememberEditListState(currentTiles, columns, largeTilesSpan)
         DefaultEditTileGrid(
             listState = currentListState,
             otherTiles = otherTiles,
@@ -154,6 +157,7 @@
             onResize = iconTilesViewModel::resize,
             onStopEditing = onStopEditing,
             onReset = viewModel::showResetDialog,
+            largeTilesSpan = largeTilesSpan,
         )
     }
 
@@ -171,7 +175,7 @@
             .map { it.flatten().map { it.tile } }
     }
 
-    private fun TileSpec.width(): Int {
-        return if (iconTilesViewModel.isIconTile(this)) 1 else 2
+    private fun TileSpec.width(largeSize: Int = iconTilesViewModel.largeTilesSpan.value): Int {
+        return if (iconTilesViewModel.isIconTile(this)) 1 else largeSize
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 5bebdbc..9bbf290 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -156,6 +156,14 @@
                     bounceEnd = currentBounceableInfo.bounceEnd,
                 ),
     ) { expandable ->
+        val longClick: (() -> Unit)? =
+            {
+                    hapticsViewModel?.setTileInteractionState(
+                        TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+                    )
+                    tile.onLongClick(expandable)
+                }
+                .takeIf { uiState.handlesLongClick }
         TileContainer(
             onClick = {
                 tile.onClick(expandable)
@@ -166,12 +174,7 @@
                     coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
                 }
             },
-            onLongClick = {
-                hapticsViewModel?.setTileInteractionState(
-                    TileHapticsViewModel.TileInteractionState.LONG_CLICKED
-                )
-                tile.onLongClick(expandable)
-            },
+            onLongClick = longClick,
             uiState = uiState,
             iconOnly = iconOnly,
         ) {
@@ -192,14 +195,6 @@
                             tile.onSecondaryClick()
                         }
                         .takeIf { uiState.handlesSecondaryClick }
-                val longClick: (() -> Unit)? =
-                    {
-                            hapticsViewModel?.setTileInteractionState(
-                                TileHapticsViewModel.TileInteractionState.LONG_CLICKED
-                            )
-                            tile.onLongClick(expandable)
-                        }
-                        .takeIf { uiState.handlesLongClick }
                 LargeTileContent(
                     label = uiState.label,
                     secondaryLabel = uiState.secondaryLabel,
@@ -237,7 +232,7 @@
 @Composable
 fun TileContainer(
     onClick: () -> Unit,
-    onLongClick: () -> Unit,
+    onLongClick: (() -> Unit)?,
     uiState: TileUiState,
     iconOnly: Boolean,
     content: @Composable BoxScope.() -> Unit,
@@ -281,7 +276,7 @@
 @Composable
 fun Modifier.tileCombinedClickable(
     onClick: () -> Unit,
-    onLongClick: () -> Unit,
+    onLongClick: (() -> Unit)?,
     uiState: TileUiState,
     iconOnly: Boolean,
 ): Modifier {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
index 9552aa9..41c3de5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
@@ -22,7 +22,7 @@
 import androidx.compose.runtime.setValue
 import com.android.systemui.qs.panels.ui.compose.selection.ResizingDefaults.RESIZING_THRESHOLD
 
-class ResizingState(private val widths: TileWidths, private val onResize: () -> Unit) {
+class ResizingState(val widths: TileWidths, private val onResize: () -> Unit) {
     /** Total drag offset of this resize operation. */
     private var totalOffset by mutableFloatStateOf(0f)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt
index 9feaab8..a9d673a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DynamicIconTilesViewModel.kt
@@ -17,9 +17,13 @@
 package com.android.systemui.qs.panels.ui.viewmodel
 
 import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.qs.panels.domain.interactor.DynamicIconTilesInteractor
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
 /** View model to resize QS tiles down to icons when removed from the current tiles. */
 class DynamicIconTilesViewModel
@@ -28,10 +32,21 @@
     interactorFactory: DynamicIconTilesInteractor.Factory,
     iconTilesViewModel: IconTilesViewModel,
 ) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() {
+    private val hydrator = Hydrator("DynamicIconTilesViewModel")
     private val interactor = interactorFactory.create()
 
+    val largeTilesSpanState =
+        hydrator.hydratedStateOf(
+            traceName = "largeTilesSpan",
+            source = iconTilesViewModel.largeTilesSpan,
+        )
+
     override suspend fun onActivated(): Nothing {
-        interactor.activate()
+        coroutineScope {
+            launch { hydrator.activate() }
+            launch { interactor.activate() }
+            awaitCancellation()
+        }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index 4e698ed..b8c5fbb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -25,6 +25,8 @@
 interface IconTilesViewModel {
     val largeTiles: StateFlow<Set<TileSpec>>
 
+    val largeTilesSpan: StateFlow<Int>
+
     fun isIconTile(spec: TileSpec): Boolean
 
     fun resize(spec: TileSpec, toIcon: Boolean)
@@ -34,6 +36,7 @@
 class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) :
     IconTilesViewModel {
     override val largeTiles = interactor.largeTilesSpecs
+    override val largeTilesSpan = interactor.largeTilesSpan
 
     override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index 33ce551..adc4e4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -70,18 +70,21 @@
             source = quickQuickSettingsRowInteractor.rows,
         )
 
+    private val largeTilesSpan by
+        hydrator.hydratedStateOf(
+            traceName = "largeTilesSpan",
+            source = iconTilesViewModel.largeTilesSpan,
+        )
+
     private val currentTiles by
         hydrator.hydratedStateOf(traceName = "currentTiles", source = tilesInteractor.currentTiles)
 
     val tileViewModels by derivedStateOf {
         currentTiles
-            .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
+            .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width()) }
             .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
     }
 
-    private val TileSpec.width: Int
-        get() = if (largeTiles.contains(this)) 2 else 1
-
     override suspend fun onActivated(): Nothing {
         coroutineScope {
             launch { hydrator.activate() }
@@ -95,4 +98,6 @@
     interface Factory {
         fun create(): QuickQuickSettingsViewModel
     }
+
+    private fun TileSpec.width(): Int = if (largeTiles.contains(this)) largeTilesSpan else 1
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
index f33b76b..ff4760f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -18,8 +18,10 @@
 
 import android.view.Display
 import android.view.View
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.DarkIconDispatcher
@@ -46,12 +48,12 @@
 import dagger.assisted.AssistedInject
 import java.io.PrintWriter
 import java.util.Optional
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChangedBy
 import kotlinx.coroutines.flow.filterNotNull
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Class responsible for managing the lifecycle and state of the status bar.
@@ -68,6 +70,7 @@
     @Assisted private val statusBarModeRepository: StatusBarModePerDisplayRepository,
     @Assisted private val statusBarInitializer: StatusBarInitializer,
     @Assisted private val statusBarWindowController: StatusBarWindowController,
+    @Main private val mainContext: CoroutineContext,
     private val demoModeController: DemoModeController,
     private val pluginDependencyProvider: PluginDependencyProvider,
     private val autoHideController: AutoHideController,
@@ -141,7 +144,8 @@
     override fun start() {
         StatusBarConnectedDisplays.assertInNewMode()
         coroutineScope
-            .launch {
+            // Perform animations on the main thread to prevent crashes.
+            .launch(context = mainContext) {
                 dumpManager.registerCriticalDumpable(dumpableName, this@StatusBarOrchestrator)
                 launch {
                     controllerAndBouncerShowing.collect { (controller, bouncerShowing) ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
index 9580016..1f8d365 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionCache.kt
@@ -102,6 +102,10 @@
                 return --lives <= 0
             }
         }
+
+        override fun toString(): String {
+            return "$key = $value"
+        }
     }
 
     /**
@@ -174,7 +178,10 @@
 
         pw.println("$TAG(retainCount = $retainCount, purgeTimeoutMillis = $purgeTimeoutMillis)")
         pw.withIncreasedIndent {
-            pw.printCollection("keys present in cache", cache.keys.stream().sorted().toList())
+            pw.printCollection(
+                "entries present in cache",
+                cache.values.stream().map { it.toString() }.sorted().toList(),
+            )
 
             val misses = misses.get()
             val hits = hits.get()
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 96f4a60..b4c6952 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -46,7 +46,6 @@
 
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -222,7 +221,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_NOTIFICATION_PULSING_FIX)
     public void testOnNotification_alreadyPulsing_notificationNotSuppressed() {
         // GIVEN device is pulsing
         Runnable pulseSuppressListener = mock(Runnable.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 8a6df1c..d88d69d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -63,6 +63,7 @@
             listState = listState,
             otherTiles = listOf(),
             columns = 4,
+            largeTilesSpan = 4,
             modifier = Modifier.fillMaxSize(),
             onRemoveTile = {},
             onSetTiles = onSetTiles,
@@ -75,7 +76,7 @@
     @Test
     fun draggedTile_shouldDisappear() {
         var tiles by mutableStateOf(TestEditTiles)
-        val listState = EditTileListState(tiles, 4)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
             EditTileGridUnderTest(listState) {
                 tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
@@ -101,7 +102,7 @@
     @Test
     fun draggedTile_shouldChangePosition() {
         var tiles by mutableStateOf(TestEditTiles)
-        val listState = EditTileListState(tiles, 4)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
             EditTileGridUnderTest(listState) {
                 tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
@@ -128,7 +129,7 @@
     @Test
     fun draggedTileOut_shouldBeRemoved() {
         var tiles by mutableStateOf(TestEditTiles)
-        val listState = EditTileListState(tiles, 4)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
             EditTileGridUnderTest(listState) {
                 tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
@@ -153,7 +154,7 @@
     @Test
     fun draggedNewTileIn_shouldBeAdded() {
         var tiles by mutableStateOf(TestEditTiles)
-        val listState = EditTileListState(tiles, 4)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
             EditTileGridUnderTest(listState) {
                 tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index d9c1d99..fac5ecb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -62,6 +62,7 @@
             listState = listState,
             otherTiles = listOf(),
             columns = 4,
+            largeTilesSpan = 4,
             modifier = Modifier.fillMaxSize(),
             onRemoveTile = {},
             onSetTiles = {},
@@ -74,7 +75,7 @@
     @Test
     fun toggleIconTile_shouldBeLarge() {
         var tiles by mutableStateOf(TestEditTiles)
-        val listState = EditTileListState(tiles, 4)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
             EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
         }
@@ -90,7 +91,7 @@
     @Test
     fun toggleLargeTile_shouldBeIcon() {
         var tiles by mutableStateOf(TestEditTiles)
-        val listState = EditTileListState(tiles, 4)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
             EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
         }
@@ -106,7 +107,7 @@
     @Test
     fun resizedLarge_shouldBeIcon() {
         var tiles by mutableStateOf(TestEditTiles)
-        val listState = EditTileListState(tiles, 4)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
             EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
         }
@@ -126,7 +127,7 @@
     @Test
     fun resizedIcon_shouldBeLarge() {
         var tiles by mutableStateOf(TestEditTiles)
-        val listState = EditTileListState(tiles, 4)
+        val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
         composeRule.setContent {
             EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
         }
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 48106de..fc318d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -2395,7 +2395,8 @@
 
         FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
         mBubbleController.registerBubbleStateListener(bubbleStateListener);
-        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+                BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
         assertThat(bubbleStateListener.mLastUpdate).isNotNull();
         assertThat(bubbleStateListener.mLastUpdate.bubbleBarLocation).isEqualTo(
                 BubbleBarLocation.LEFT);
@@ -2408,7 +2409,8 @@
 
         FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
         mBubbleController.registerBubbleStateListener(bubbleStateListener);
-        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+                BubbleBarLocation.UpdateSource.DRAG_EXP_VIEW);
         assertThat(bubbleStateListener.mStateChangeCalls).isEqualTo(0);
     }
 
@@ -2535,6 +2537,78 @@
 
     @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
     @Test
+    public void testEventLogging_bubbleBar_dragBarLeft() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        assertBarMode();
+
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+                BubbleBarLocation.UpdateSource.DRAG_BAR);
+
+        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR);
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_dragBarRight() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        assertBarMode();
+
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
+                BubbleBarLocation.UpdateSource.DRAG_BAR);
+
+        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_dragBubbleLeft() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        assertBarMode();
+
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
+                BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
+
+        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE);
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_dragBubbleRight() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        assertBarMode();
+
+        mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
+                BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
+
+        verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
     public void testEventLogging_bubbleBar_expandAndCollapse() {
         mBubbleProperties.mIsBubbleBarEnabled = true;
         mPositioner.setIsLargeScreen(true);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index b76fa35..e4c7df6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
 import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.trustInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -38,6 +39,7 @@
             biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
             systemPropertiesHelper = fakeSystemPropertiesHelper,
             userAwareSecureSettingsRepository = userAwareSecureSettingsRepository,
+            keyguardInteractor = keyguardInteractor,
         )
         .apply { activateIn(testScope) }
 }
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 19e077c..8209ee1 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
@@ -87,7 +87,7 @@
     ) : this(
         initInLockscreen = true,
         initiallySendTransitionStepsOnStartTransition = true,
-        testScope
+        testScope,
     )
 
     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
@@ -191,12 +191,12 @@
         if (lastStep != null && lastStep.transitionState != TransitionState.FINISHED) {
             sendTransitionStep(
                 step =
-                TransitionStep(
-                    transitionState = TransitionState.CANCELED,
-                    from = lastStep.from,
-                    to = lastStep.to,
-                    value = 0f,
-                )
+                    TransitionStep(
+                        transitionState = TransitionState.CANCELED,
+                        from = lastStep.from,
+                        to = lastStep.to,
+                        value = 0f,
+                    )
             )
             testScheduler.runCurrent()
         }
@@ -390,6 +390,18 @@
         @FloatRange(from = 0.0, to = 1.0) value: Float,
         state: TransitionState,
     ) = Unit
+
+    override suspend fun forceFinishCurrentTransition() {
+        _transitions.tryEmit(
+            TransitionStep(
+                _currentTransitionInfo.value.from,
+                _currentTransitionInfo.value.to,
+                1f,
+                TransitionState.FINISHED,
+                ownerName = _currentTransitionInfo.value.ownerName,
+            )
+        )
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index aa94c36..b9a831f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 
 val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
@@ -26,6 +27,7 @@
         KeyguardTransitionInteractor(
             scope = applicationCoroutineScope,
             repository = keyguardTransitionRepository,
-            sceneInteractor = sceneInteractor
+            sceneInteractor = sceneInteractor,
+            powerInteractor = powerInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepositoryKosmos.kt
new file mode 100644
index 0000000..a977121
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/LargeTileSpanRepositoryKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import android.content.res.mainResources
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+
+val Kosmos.largeTileSpanRepository by
+    Kosmos.Fixture {
+        LargeTileSpanRepository(applicationCoroutineScope, mainResources, configurationRepository)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
index 0c62d0e..8d4db8b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
+import com.android.systemui.qs.panels.data.repository.largeTileSpanRepository
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 
 val Kosmos.iconTilesInteractor by
@@ -28,7 +29,8 @@
             defaultLargeTilesRepository,
             currentTilesInteractor,
             qsPreferencesInteractor,
+            largeTileSpanRepository,
             FakeLogBuffer.Factory.create(),
-            applicationCoroutineScope
+            applicationCoroutineScope,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 45aab86..28edae7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -49,6 +49,7 @@
             fakeStatusBarModePerDisplayRepository,
             fakeStatusBarInitializer,
             fakeStatusBarWindowController,
+            applicationCoroutineScope.coroutineContext,
             mockDemoModeController,
             mockPluginDependencyProvider,
             mockAutoHideController,
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index cee29dc..7a7de35 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -47,6 +47,7 @@
 
     # Test to check the generated jar files to the golden output.
     def test_compare_to_golden(self):
+        self.skipTest("test cannot handle multiple images (see b/378470825)")
         files = os.listdir(GOLDEN_DIR)
         files.sort()
 
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 23cee9d..1588e04 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -53,6 +53,7 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -400,9 +401,18 @@
             Slog.w(TAG, "Tombstone too large to add to DropBox: " + tombstone.toPath());
             return;
         }
-        // Read the proto tombstone file as bytes.
-        final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
 
+        // Read the proto tombstone file as bytes.
+        // Previously used Files.readAllBytes() which internally creates a ThreadLocal BufferCache
+        // via ChannelInputStream that isn't properly released. Switched to
+        // FileInputStream.transferTo() which avoids the NIO channels completely,
+        // preventing the memory leak while maintaining the same functionality.
+        final byte[] tombstoneBytes;
+        try (FileInputStream fis = new FileInputStream(tombstone);
+                ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            fis.transferTo(baos);
+            tombstoneBytes = baos.toByteArray();
+        }
         final File tombstoneProtoWithHeaders = File.createTempFile(
                 tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
         Files.setPosixFilePermissions(
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 116aeea..38df10a 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -44,7 +44,6 @@
 import android.app.BroadcastOptions.DeliveryGroupPolicy;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
-import android.compat.annotation.Overridable;
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.Intent;
@@ -88,7 +87,6 @@
      */
     @ChangeId
     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
-    @Overridable
     @VisibleForTesting
     static final long CHANGE_LIMIT_PRIORITY_SCOPE = 371307720L;
 
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index f4a931f..d2af84c 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -19,7 +19,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.am.ActivityManagerService.checkComponentPermission;
 import static com.android.server.am.BroadcastQueue.TAG;
-import static com.android.server.am.Flags.usePermissionManagerForBroadcastDeliveryCheck;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -289,33 +288,16 @@
 
         if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
                 r.requiredPermissions != null && r.requiredPermissions.length > 0) {
-            final AttributionSource[] attributionSources;
-            if (usePermissionManagerForBroadcastDeliveryCheck()) {
-                attributionSources = createAttributionSourcesForResolveInfo(info);
-            } else {
-                attributionSources = null;
-            }
+            final AttributionSource[] attributionSources =
+                    createAttributionSourcesForResolveInfo(info);
             for (int i = 0; i < r.requiredPermissions.length; i++) {
                 String requiredPermission = r.requiredPermissions[i];
-                try {
-                    if (usePermissionManagerForBroadcastDeliveryCheck()) {
-                        perm = hasPermissionForDataDelivery(
-                                requiredPermission,
-                                "Broadcast delivered to " + info.activityInfo.name,
-                                attributionSources)
-                                        ? PackageManager.PERMISSION_GRANTED
-                                        : PackageManager.PERMISSION_DENIED;
-                    } else {
-                        perm = AppGlobals.getPackageManager()
-                                .checkPermission(
-                                        requiredPermission,
-                                        info.activityInfo.applicationInfo.packageName,
-                                        UserHandle
-                                                .getUserId(info.activityInfo.applicationInfo.uid));
-                    }
-                } catch (RemoteException e) {
-                    perm = PackageManager.PERMISSION_DENIED;
-                }
+                perm = hasPermissionForDataDelivery(
+                        requiredPermission,
+                        "Broadcast delivered to " + info.activityInfo.name,
+                        attributionSources)
+                                ? PackageManager.PERMISSION_GRANTED
+                                : PackageManager.PERMISSION_DENIED;
                 if (perm != PackageManager.PERMISSION_GRANTED) {
                     return "Permission Denial: receiving "
                             + r.intent + " to "
@@ -324,15 +306,6 @@
                             + " due to sender " + r.callerPackage
                             + " (uid " + r.callingUid + ")";
                 }
-                if (!usePermissionManagerForBroadcastDeliveryCheck()) {
-                    int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
-                    if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
-                        if (!noteOpForManifestReceiver(appOp, r, info, component)) {
-                            return "Skipping delivery to " + info.activityInfo.packageName
-                                    + " due to required appop " + appOp;
-                        }
-                    }
-                }
             }
         }
         if (r.appOp != AppOpsManager.OP_NONE) {
@@ -452,35 +425,20 @@
 
         // Check that the receiver has the required permission(s) to receive this broadcast.
         if (r.requiredPermissions != null && r.requiredPermissions.length > 0) {
-            final AttributionSource attributionSource;
-            if (usePermissionManagerForBroadcastDeliveryCheck()) {
-                attributionSource =
-                        new AttributionSource.Builder(filter.receiverList.uid)
-                                .setPid(filter.receiverList.pid)
-                                .setPackageName(filter.packageName)
-                                .setAttributionTag(filter.featureId)
-                                .build();
-            } else {
-                attributionSource = null;
-            }
+            final AttributionSource attributionSource =
+                    new AttributionSource.Builder(filter.receiverList.uid)
+                            .setPid(filter.receiverList.pid)
+                            .setPackageName(filter.packageName)
+                            .setAttributionTag(filter.featureId)
+                            .build();
             for (int i = 0; i < r.requiredPermissions.length; i++) {
                 String requiredPermission = r.requiredPermissions[i];
-                final int perm;
-                if (usePermissionManagerForBroadcastDeliveryCheck()) {
-                    perm = hasPermissionForDataDelivery(
-                            requiredPermission,
-                            "Broadcast delivered to registered receiver " + filter.receiverId,
-                            attributionSource)
-                                    ? PackageManager.PERMISSION_GRANTED
-                                    : PackageManager.PERMISSION_DENIED;
-                } else {
-                    perm = checkComponentPermission(
-                            requiredPermission,
-                            filter.receiverList.pid,
-                            filter.receiverList.uid,
-                            -1 /* owningUid */,
-                            true /* exported */);
-                }
+                final int perm = hasPermissionForDataDelivery(
+                        requiredPermission,
+                        "Broadcast delivered to registered receiver " + filter.receiverId,
+                        attributionSource)
+                                ? PackageManager.PERMISSION_GRANTED
+                                : PackageManager.PERMISSION_DENIED;
                 if (perm != PackageManager.PERMISSION_GRANTED) {
                     return "Permission Denial: receiving "
                             + r.intent.toString()
@@ -491,24 +449,6 @@
                             + " due to sender " + r.callerPackage
                             + " (uid " + r.callingUid + ")";
                 }
-                if (!usePermissionManagerForBroadcastDeliveryCheck()) {
-                    int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
-                    if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
-                            && mService.getAppOpsManager().noteOpNoThrow(appOp,
-                            filter.receiverList.uid, filter.packageName, filter.featureId,
-                            "Broadcast delivered to registered receiver " + filter.receiverId)
-                            != AppOpsManager.MODE_ALLOWED) {
-                        return "Appop Denial: receiving "
-                                + r.intent.toString()
-                                + " to " + filter.receiverList.app
-                                + " (pid=" + filter.receiverList.pid
-                                + ", uid=" + filter.receiverList.uid + ")"
-                                + " requires appop " + AppOpsManager.permissionToOp(
-                                requiredPermission)
-                                + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")";
-                    }
-                }
             }
         }
         if ((r.requiredPermissions == null || r.requiredPermissions.length == 0)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 09de894..34d4fb0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -40,6 +40,7 @@
 import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.content.Intent;
+import android.media.AudioDescriptor;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioDevicePort;
@@ -47,6 +48,7 @@
 import android.media.AudioManager;
 import android.media.AudioManager.AudioDeviceCategory;
 import android.media.AudioPort;
+import android.media.AudioProfile;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
@@ -619,6 +621,8 @@
         final int mGroupId;
         @NonNull String mPeerDeviceAddress;
         @NonNull String mPeerIdentityDeviceAddress;
+        @NonNull List<AudioProfile> mAudioProfiles;
+        @NonNull List<AudioDescriptor> mAudioDescriptors;
 
         /** Disabled operating modes for this device. Use a negative logic so that by default
          * an empty list means all modes are allowed.
@@ -627,7 +631,8 @@
 
         DeviceInfo(int deviceType, String deviceName, String address,
                    String identityAddress, int codecFormat,
-                   int groupId, String peerAddress, String peerIdentityAddress) {
+                   int groupId, String peerAddress, String peerIdentityAddress,
+                   List<AudioProfile> profiles, List<AudioDescriptor> descriptors) {
             mDeviceType = deviceType;
             mDeviceName = TextUtils.emptyIfNull(deviceName);
             mDeviceAddress = TextUtils.emptyIfNull(address);
@@ -639,6 +644,16 @@
             mGroupId = groupId;
             mPeerDeviceAddress = TextUtils.emptyIfNull(peerAddress);
             mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(peerIdentityAddress);
+            mAudioProfiles = profiles;
+            mAudioDescriptors = descriptors;
+        }
+
+        DeviceInfo(int deviceType, String deviceName, String address,
+                   String identityAddress, int codecFormat,
+                   int groupId, String peerAddress, String peerIdentityAddress) {
+            this(deviceType, deviceName, address, identityAddress, codecFormat,
+                    groupId, peerAddress, peerIdentityAddress,
+                    new ArrayList<>(), new ArrayList<>());
         }
 
         /** Constructor for all devices except A2DP sink and LE Audio */
@@ -646,6 +661,13 @@
             this(deviceType, deviceName, address, null, AudioSystem.AUDIO_FORMAT_DEFAULT);
         }
 
+        /** Constructor for HDMI OUT, HDMI ARC/EARC sink devices */
+        DeviceInfo(int deviceType, String deviceName, String address,
+            List<AudioProfile> profiles, List<AudioDescriptor> descriptors) {
+            this(deviceType, deviceName, address, null, AudioSystem.AUDIO_FORMAT_DEFAULT,
+                BluetoothLeAudio.GROUP_ID_INVALID, null, null, profiles, descriptors);
+        }
+
         /** Constructor for A2DP sink devices */
         DeviceInfo(int deviceType, String deviceName, String address,
                    String identityAddress, int codecFormat) {
@@ -1194,27 +1216,31 @@
     }
 
     /*package*/ void onToggleHdmi() {
-        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi")
-                .set(MediaMetrics.Property.DEVICE,
-                        AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HDMI));
+        final int[] hdmiDevices = { AudioSystem.DEVICE_OUT_HDMI,
+                AudioSystem.DEVICE_OUT_HDMI_ARC, AudioSystem.DEVICE_OUT_HDMI_EARC };
+
         synchronized (mDevicesLock) {
-            // Is HDMI connected?
-            final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
-            final DeviceInfo di = mConnectedDevices.get(key);
-            if (di == null) {
-                Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
-                mmi.set(MediaMetrics.Property.EARLY_RETURN, "invalid null DeviceInfo").record();
-                return;
+            for (DeviceInfo di : mConnectedDevices.values()) {
+                boolean isHdmiDevice = Arrays.stream(hdmiDevices).anyMatch(device ->
+                    device == di.mDeviceType);
+                if (isHdmiDevice) {
+                    MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi")
+                            .set(MediaMetrics.Property.DEVICE,
+                                    AudioSystem.getDeviceName(di.mDeviceType));
+                    AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                            AudioDeviceAttributes.ROLE_OUTPUT,
+                            AudioDeviceInfo.convertInternalDeviceToDeviceType(di.mDeviceType),
+                            di.mDeviceAddress, di.mDeviceName, di.mAudioProfiles,
+                            di.mAudioDescriptors);
+                    // Toggle HDMI to retrigger broadcast with proper formats.
+                    setWiredDeviceConnectionState(ada,
+                            AudioSystem.DEVICE_STATE_UNAVAILABLE, "onToggleHdmi"); // disconnect
+                    setWiredDeviceConnectionState(ada,
+                            AudioSystem.DEVICE_STATE_AVAILABLE, "onToggleHdmi"); // reconnect
+                    mmi.record();
+                }
             }
-            // Toggle HDMI to retrigger broadcast with proper formats.
-            setWiredDeviceConnectionState(
-                    new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
-                    AudioSystem.DEVICE_STATE_UNAVAILABLE, "android"); // disconnect
-            setWiredDeviceConnectionState(
-                    new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
-                    AudioSystem.DEVICE_STATE_AVAILABLE, "android"); // reconnect
         }
-        mmi.record();
     }
 
     @GuardedBy("mDevicesLock")
@@ -1818,7 +1844,15 @@
                             .printSlog(EventLogger.Event.ALOGE, TAG));
                     return false;
                 }
-                mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
+
+                if (device == AudioSystem.DEVICE_OUT_HDMI ||
+                    device == AudioSystem.DEVICE_OUT_HDMI_ARC ||
+                    device == AudioSystem.DEVICE_OUT_HDMI_EARC) {
+                    mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName,
+                        address, attributes.getAudioProfiles(), attributes.getAudioDescriptors()));
+                } else {
+                    mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
+                }
                 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
                 status = true;
             } else if (!connect && isConnected) {
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index c0aa4cc..71f17d1 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -242,6 +242,11 @@
             Flags::autoBrightnessModeBedtimeWear
     );
 
+    private final FlagState mEnablePluginManagerFlagState = new FlagState(
+            Flags.FLAG_ENABLE_PLUGIN_MANAGER,
+            Flags::enablePluginManager
+    );
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -517,6 +522,10 @@
         return mAutoBrightnessModeBedtimeWearFlagState.isEnabled();
     }
 
+    public boolean isPluginManagerEnabled() {
+        return mEnablePluginManagerFlagState.isEnabled();
+    }
+
     /**
      * dumps all flagstates
      * @param pw printWriter
@@ -568,6 +577,7 @@
         pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
         pw.println(" " + mHasArrSupport);
         pw.println(" " + mAutoBrightnessModeBedtimeWearFlagState);
+        pw.println(" " + mEnablePluginManagerFlagState);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index a9bdcce..7850360 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -446,3 +446,11 @@
     bug: "365163968"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_plugin_manager"
+    namespace: "display_manager"
+    description: "Flag to enable DisplayManager plugins"
+    bug: "354059797"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
new file mode 100644
index 0000000..745665b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.parsing.PackageLite;
+import android.os.OutcomeReceiver;
+
+import java.util.List;
+
+/**
+ * Helper class to interact with SDK Dependency Installer service.
+ */
+public class InstallDependencyHelper {
+    private final SharedLibrariesImpl mSharedLibraries;
+
+    InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) {
+        mSharedLibraries = sharedLibraries;
+    }
+
+    void resolveLibraryDependenciesIfNeeded(PackageLite pkg,
+            OutcomeReceiver<Void, PackageManagerException> callback) {
+        final List<SharedLibraryInfo> missing;
+        try {
+            missing = mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+        } catch (PackageManagerException e) {
+            callback.onError(e);
+            return;
+        }
+
+        if (missing.isEmpty()) {
+            // No need for dependency resolution. Move to installation directly.
+            callback.onResult(null);
+            return;
+        }
+
+        try {
+            bindToDependencyInstaller();
+        } catch (Exception e) {
+            PackageManagerException pe = new PackageManagerException(
+                    INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage());
+            callback.onError(pe);
+        }
+    }
+
+    private void bindToDependencyInstaller() {
+        throw new IllegalStateException("Failed to bind to Dependency Installer");
+    }
+
+
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ef09976..eb70748 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -220,6 +220,7 @@
 
     private AppOpsManager mAppOps;
     private final VerifierController mVerifierController;
+    private final InstallDependencyHelper mInstallDependencyHelper;
 
     private final HandlerThread mInstallThread;
     private final Handler mInstallHandler;
@@ -346,6 +347,8 @@
         synchronized (mVerificationPolicyPerUser) {
             mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY);
         }
+        mInstallDependencyHelper = new InstallDependencyHelper(
+                mPm.mInjector.getSharedLibrariesImpl());
 
         LocalServices.getService(SystemServiceManager.class).startService(
                 new Lifecycle(context, this));
@@ -543,7 +546,7 @@
                             session = PackageInstallerSession.readFromXml(in, mInternalCallback,
                                     mContext, mPm, mInstallThread.getLooper(), mStagingManager,
                                     mSessionsDir, this, mSilentUpdatePolicy,
-                                    mVerifierController);
+                                    mVerifierController, mInstallDependencyHelper);
                         } catch (Exception e) {
                             Slog.e(TAG, "Could not read session", e);
                             continue;
@@ -1065,7 +1068,8 @@
                 userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
                 null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
                 false, false, false, PackageManager.INSTALL_UNKNOWN, "", null,
-                mVerifierController, verificationPolicy, verificationPolicy);
+                mVerifierController, verificationPolicy, verificationPolicy,
+                mInstallDependencyHelper);
 
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2a92de5..bad1201 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -145,6 +145,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelableException;
 import android.os.PersistableBundle;
@@ -433,6 +434,8 @@
     private final StagingManager mStagingManager;
     @NonNull private final VerifierController mVerifierController;
 
+    private final InstallDependencyHelper mInstallDependencyHelper;
+
     final int sessionId;
     final int userId;
     final SessionParams params;
@@ -1188,7 +1191,8 @@
             String sessionErrorMessage, DomainSet preVerifiedDomains,
             @NonNull VerifierController verifierController,
             @PackageInstaller.VerificationPolicy int initialVerificationPolicy,
-            @PackageInstaller.VerificationPolicy int currentVerificationPolicy) {
+            @PackageInstaller.VerificationPolicy int currentVerificationPolicy,
+            InstallDependencyHelper installDependencyHelper) {
         mCallback = callback;
         mContext = context;
         mPm = pm;
@@ -1200,6 +1204,7 @@
         mVerifierController = verifierController;
         mInitialVerificationPolicy = initialVerificationPolicy;
         mCurrentVerificationPolicy = new AtomicInteger(currentVerificationPolicy);
+        mInstallDependencyHelper = installDependencyHelper;
 
         this.sessionId = sessionId;
         this.userId = userId;
@@ -2611,6 +2616,13 @@
         maybeFinishChildSessions(error, msg);
     }
 
+    private void onSessionDependencyResolveFailure(int error, String msg) {
+        Slog.e(TAG, "Failed to resolve dependency for session " + sessionId);
+        // Dispatch message to remove session from PackageInstallerService.
+        dispatchSessionFinished(error, msg, null);
+        maybeFinishChildSessions(error, msg);
+    }
+
     private void onSystemDataLoaderUnrecoverable() {
         final String packageName = getPackageName();
         if (TextUtils.isEmpty(packageName)) {
@@ -3402,7 +3414,34 @@
                     /* extras= */ null, /* forPreapproval= */ false);
             return;
         }
-        install();
+
+        if (Flags.sdkDependencyInstaller() && !isMultiPackage()) {
+            resolveLibraryDependenciesIfNeeded();
+        } else {
+            install();
+        }
+    }
+
+
+    private void resolveLibraryDependenciesIfNeeded() {
+        synchronized (mLock) {
+            // TODO(b/372862145): Callback should be called on a handler passed as parameter
+            mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
+                    new OutcomeReceiver<>() {
+
+                        @Override
+                        public void onResult(Void result) {
+                            install();
+                        }
+
+                        @Override
+                        public void onError(@NonNull PackageManagerException e) {
+                            final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+                            setSessionFailed(e.error, completeMsg);
+                            onSessionDependencyResolveFailure(e.error, completeMsg);
+                        }
+                    });
+        }
     }
 
     /**
@@ -6048,7 +6087,8 @@
             @NonNull StagingManager stagingManager, @NonNull File sessionsDir,
             @NonNull PackageSessionProvider sessionProvider,
             @NonNull SilentUpdatePolicy silentUpdatePolicy,
-            @NonNull VerifierController verifierController)
+            @NonNull VerifierController verifierController,
+            @NonNull InstallDependencyHelper installDependencyHelper)
             throws IOException, XmlPullParserException {
         final int sessionId = in.getAttributeInt(null, ATTR_SESSION_ID);
         final int userId = in.getAttributeInt(null, ATTR_USER_ID);
@@ -6257,6 +6297,6 @@
                 stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
                 childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
                 sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController,
-                initialVerificationPolicy, currentVerificationPolicy);
+                initialVerificationPolicy, currentVerificationPolicy, installDependencyHelper);
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index a28e3c1..52e8c52 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -38,6 +38,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
@@ -45,7 +46,8 @@
 
 /** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly
  * used by PackageMonitor to improve the broadcast latency. */
-class PackageMonitorCallbackHelper {
+@VisibleForTesting
+public class PackageMonitorCallbackHelper {
 
     private static final boolean DEBUG = false;
     private static final String TAG = "PackageMonitorCallbackHelper";
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 929fccc..fc54f68 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -33,6 +33,7 @@
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
 import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.PackageLite;
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
@@ -83,6 +84,7 @@
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
 
     private static final String LIBRARY_TYPE_SDK = "sdk";
+    private static final String LIBRARY_TYPE_STATIC = "static shared";
 
     /**
      * Apps targeting Android S and above need to declare dependencies to the public native
@@ -926,18 +928,19 @@
         if (!pkg.getUsesLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null,
                     pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
-                    availablePackages, newLibraries);
+                    availablePackages, newLibraries, null);
         }
         if (!pkg.getUsesStaticLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
                     pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
-                    null, pkg.getPackageName(), "static shared", true,
-                    pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries);
+                    null, pkg.getPackageName(), LIBRARY_TYPE_STATIC, true,
+                    pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries,
+                    null);
         }
         if (!pkg.getUsesOptionalLibraries().isEmpty()) {
             usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
                     null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
-                    usesLibraryInfos, availablePackages, newLibraries);
+                    usesLibraryInfos, availablePackages, newLibraries, null);
         }
         if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
                 pkg.getPackageName(), pkg.getTargetSdkVersion())) {
@@ -945,13 +948,13 @@
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
                         null, null, pkg.getPackageName(), "native shared", true,
                         pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
-                        newLibraries);
+                        newLibraries, null);
             }
             if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
                         null, null, null, pkg.getPackageName(), "native shared", false,
                         pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
-                        newLibraries);
+                        newLibraries, null);
             }
         }
         if (!pkg.getUsesSdkLibraries().isEmpty()) {
@@ -961,11 +964,34 @@
                     pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
                     pkg.getUsesSdkLibrariesOptional(),
                     pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(),
-                    usesLibraryInfos, availablePackages, newLibraries);
+                    usesLibraryInfos, availablePackages, newLibraries, null);
         }
         return usesLibraryInfos;
     }
 
+    List<SharedLibraryInfo> collectMissingSharedLibraryInfos(PackageLite pkgLite)
+            throws PackageManagerException {
+        ArrayList<SharedLibraryInfo> missingSharedLibrary = new ArrayList<>();
+        synchronized (mPm.mLock) {
+            collectSharedLibraryInfos(pkgLite.getUsesSdkLibraries(),
+                    pkgLite.getUsesSdkLibrariesVersionsMajor(),
+                    pkgLite.getUsesSdkLibrariesCertDigests(),
+                    /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_SDK,
+                    /*required=*/ true, pkgLite.getTargetSdk(),
+                    /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+                    missingSharedLibrary);
+
+            collectSharedLibraryInfos(pkgLite.getUsesStaticLibraries(),
+                    pkgLite.getUsesStaticLibrariesVersions(),
+                    pkgLite.getUsesStaticLibrariesCertDigests(),
+                    /*libsOptional=*/ null, pkgLite.getPackageName(), LIBRARY_TYPE_STATIC,
+                    /*required=*/ true, pkgLite.getTargetSdk(),
+                    /*outUsedLibraries=*/ null, mPm.mPackages, /*newLibraries=*/ null,
+                    missingSharedLibrary);
+        }
+        return missingSharedLibrary;
+    }
+
     private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
             @NonNull List<String> requestedLibraries,
             @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
@@ -973,7 +999,8 @@
             @NonNull String packageName, @NonNull String libraryType, boolean required,
             int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
             @NonNull final Map<String, AndroidPackage> availablePackages,
-            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
+            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
+            @Nullable final List<SharedLibraryInfo> outMissingSharedLibraryInfos)
             throws PackageManagerException {
         final int libCount = requestedLibraries.size();
         for (int i = 0; i < libCount; i++) {
@@ -986,16 +1013,33 @@
                         libName, libVersion, mSharedLibraries, newLibraries);
             }
             if (libraryInfo == null) {
-                // Only allow app be installed if the app specifies the sdk-library dependency is
-                // optional
-                if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null
-                        && !libsOptional[i]))) {
-                    throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
-                            "Package " + packageName + " requires unavailable " + libraryType
-                                    + " library " + libName + "; failing!");
-                } else if (DEBUG_SHARED_LIBRARIES) {
-                    Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
-                            + " library " + libName + "; ignoring!");
+                if (required) {
+                    boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK)
+                            || libraryType.equals(LIBRARY_TYPE_STATIC);
+                    if (isSdkOrStatic && outMissingSharedLibraryInfos != null) {
+                        // TODO(b/372862145): Pass the CertDigest too
+                        // If Dependency Installation is supported, try that instead of failing.
+                        SharedLibraryInfo missingLibrary = new SharedLibraryInfo(
+                                libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE
+                        );
+                        outMissingSharedLibraryInfos.add(missingLibrary);
+                    } else {
+                        throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                "Package " + packageName + " requires unavailable " + libraryType
+                                + " library " + libName + "; failing!");
+                    }
+                } else {
+                    // Only allow app be installed if the app specifies the sdk-library
+                    // dependency is optional
+                    boolean isOptional = libsOptional != null && libsOptional[i];
+                    if (LIBRARY_TYPE_SDK.equals(libraryType) && !isOptional) {
+                        throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+                                "Package " + packageName + " requires unavailable " + libraryType
+                                + " library " + libName + "; failing!");
+                    } else if (DEBUG_SHARED_LIBRARIES) {
+                        Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
+                                + " library " + libName + "; ignoring!");
+                    }
                 }
             } else {
                 if (requiredVersions != null && requiredCertDigests != null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index d5bea4a..b3e68a35 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -19,6 +19,7 @@
 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 import static android.app.WallpaperManager.getOrientation;
 import static android.app.WallpaperManager.getRotatedOrientation;
+import static android.app.Flags.accurateWallpaperDownsampling;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.wallpaper.WallpaperUtils.RECORD_FILE;
@@ -378,7 +379,14 @@
         for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
             Rect adjustedRect = new Rect(wallpaper.mCropHints.valueAt(i));
             adjustedRect.offset(-wallpaper.cropHint.left, -wallpaper.cropHint.top);
-            adjustedRect.scale(1f / wallpaper.mSampleSize);
+            if (accurateWallpaperDownsampling()) {
+                adjustedRect.left = (int) (0.5f + adjustedRect.left / wallpaper.mSampleSize);
+                adjustedRect.top = (int) (0.5f + adjustedRect.top / wallpaper.mSampleSize);
+                adjustedRect.right = (int) Math.floor(adjustedRect.right / wallpaper.mSampleSize);
+                adjustedRect.bottom = (int) Math.floor(adjustedRect.bottom / wallpaper.mSampleSize);
+            } else {
+                adjustedRect.scale(1f / wallpaper.mSampleSize);
+            }
             result.put(wallpaper.mCropHints.keyAt(i), adjustedRect);
         }
         return result;
@@ -603,6 +611,11 @@
                     float sampleSizeForThisOrientation = Math.max(1f, Math.min(
                             crop.width() / displayForThisOrientation.x,
                             crop.height() / displayForThisOrientation.y));
+                    if (accurateWallpaperDownsampling()) {
+                        sampleSizeForThisOrientation = Math.max(1f, Math.min(
+                                (float) crop.width() / displayForThisOrientation.x,
+                                (float) crop.height() / displayForThisOrientation.y));
+                    }
                     sampleSize = Math.min(sampleSize, sampleSizeForThisOrientation);
                 }
                 // If the total crop has more width or height than either the max texture size
@@ -746,8 +759,8 @@
                     final ImageDecoder.Source srcData =
                             ImageDecoder.createSource(wallpaper.getWallpaperFile());
                     final int finalScale = scale;
-                    final int rescaledBitmapWidth = (int) (0.5f + bitmapSize.x / sampleSize);
-                    final int rescaledBitmapHeight = (int) (0.5f + bitmapSize.y / sampleSize);
+                    final int rescaledBitmapWidth = (int) Math.ceil(bitmapSize.x / sampleSize);
+                    final int rescaledBitmapHeight = (int) Math.ceil(bitmapSize.y / sampleSize);
                     Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
                         if (!multiCrop()) decoder.setTargetSampleSize(finalScale);
                         if (multiCrop()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 10f096c..d019516 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2380,8 +2380,8 @@
             SparseArray<Rect> relativeSuggestedCrops =
                     mWallpaperCropper.getRelativeCropHints(wallpaper);
             Point croppedBitmapSize = new Point(
-                    (int) (0.5f + wallpaper.cropHint.width() / wallpaper.mSampleSize),
-                    (int) (0.5f + wallpaper.cropHint.height() / wallpaper.mSampleSize));
+                    (int) Math.ceil(wallpaper.cropHint.width() / wallpaper.mSampleSize),
+                    (int) Math.ceil(wallpaper.cropHint.height() / wallpaper.mSampleSize));
             if (croppedBitmapSize.equals(0, 0)) {
                 // There is an ImageWallpaper, but there are no crop hints and the bitmap size is
                 // unknown (e.g. the default wallpaper). Return a special "null" value that will be
@@ -2410,6 +2410,27 @@
     }
 
     @Override
+    public Bundle getCurrentBitmapCrops(int which, int userId) {
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, true, "getBitmapCrop", null);
+        synchronized (mLock) {
+            checkPermission(READ_WALLPAPER_INTERNAL);
+            WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+                    : mWallpaperMap.get(userId);
+            if (wallpaper == null || !mImageWallpaper.equals(wallpaper.getComponent())) {
+                return null;
+            }
+            Bundle bundle = new Bundle();
+            for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+                String key = String.valueOf(wallpaper.mCropHints.keyAt(i));
+                Rect rect = wallpaper.mCropHints.valueAt(i);
+                bundle.putParcelable(key, rect);
+            }
+            return bundle;
+        }
+    }
+
+    @Override
     public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
             int[] screenOrientations, List<Rect> crops) {
         SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 3f6e915..9a48d5b 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -18,7 +18,6 @@
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
 import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_ADAPTER;
-import static com.android.server.wm.SurfaceAnimatorProto.ANIMATION_START_DELAYED;
 import static com.android.server.wm.SurfaceAnimatorProto.LEASH;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -90,8 +89,6 @@
     @Nullable
     private Runnable mAnimationCancelledCallback;
 
-    private boolean mAnimationStartDelayed;
-
     private boolean mAnimationFinished;
 
     /**
@@ -188,10 +185,6 @@
             mAnimatable.onAnimationLeashCreated(t, mLeash);
         }
         mAnimatable.onLeashAnimationStarting(t, mLeash);
-        if (mAnimationStartDelayed) {
-            ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);
-            return;
-        }
         mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
         if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
             StringWriter sw = new StringWriter();
@@ -215,36 +208,7 @@
                 null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */);
     }
 
-    /**
-     * Begins with delaying all animations to start. Any subsequent call to {@link #startAnimation}
-     * will not start the animation until {@link #endDelayingAnimationStart} is called. When an
-     * animation start is being delayed, the animator is considered animating already.
-     */
-    void startDelayingAnimationStart() {
-
-        // We only allow delaying animation start we are not currently animating
-        if (!isAnimating()) {
-            mAnimationStartDelayed = true;
-        }
-    }
-
-    /**
-     * See {@link #startDelayingAnimationStart}.
-     */
-    void endDelayingAnimationStart() {
-        final boolean delayed = mAnimationStartDelayed;
-        mAnimationStartDelayed = false;
-        if (delayed && mAnimation != null) {
-            mAnimation.startAnimation(mLeash, mAnimatable.getSyncTransaction(),
-                    mAnimationType, mInnerAnimationFinishedCallback);
-            mAnimatable.commitPendingTransaction();
-        }
-    }
-
-    /**
-     * @return Whether we are currently running an animation, or we have a pending animation that
-     *         is waiting to be started with {@link #endDelayingAnimationStart}
-     */
+    /** Returns whether it is currently running an animation. */
     boolean isAnimating() {
         return mAnimation != null;
     }
@@ -290,15 +254,6 @@
     }
 
     /**
-     * Reparents the surface.
-     *
-     * @see #setLayer
-     */
-    void reparent(Transaction t, SurfaceControl newParent) {
-        t.reparent(mLeash != null ? mLeash : mAnimatable.getSurfaceControl(), newParent);
-    }
-
-    /**
      * @return True if the surface is attached to the leash; false otherwise.
      */
     boolean hasLeash() {
@@ -319,7 +274,6 @@
             Slog.w(TAG, "Unable to transfer animation, because " + from + " animation is finished");
             return;
         }
-        endDelayingAnimationStart();
         final Transaction t = mAnimatable.getSyncTransaction();
         cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
         mLeash = from.mLeash;
@@ -336,10 +290,6 @@
         mService.mAnimationTransferMap.put(mAnimation, this);
     }
 
-    boolean isAnimationStartDelayed() {
-        return mAnimationStartDelayed;
-    }
-
     /**
      * Cancels the animation, and resets the leash.
      *
@@ -361,7 +311,7 @@
         final SurfaceFreezer.Snapshot snapshot = mSnapshot;
         reset(t, false);
         if (animation != null) {
-            if (!mAnimationStartDelayed && forwardCancel) {
+            if (forwardCancel) {
                 animation.onAnimationCancelled(leash);
                 if (animationCancelledCallback != null) {
                     animationCancelledCallback.run();
@@ -386,10 +336,6 @@
                 mService.scheduleAnimationLocked();
             }
         }
-
-        if (!restarting) {
-            mAnimationStartDelayed = false;
-        }
     }
 
     private void reset(Transaction t, boolean destroyLeash) {
@@ -495,14 +441,12 @@
         if (mLeash != null) {
             mLeash.dumpDebug(proto, LEASH);
         }
-        proto.write(ANIMATION_START_DELAYED, mAnimationStartDelayed);
         proto.end(token);
     }
 
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mLeash="); pw.print(mLeash);
-        pw.print(" mAnimationType=" + animationTypeToString(mAnimationType));
-        pw.println(mAnimationStartDelayed ? " mAnimationStartDelayed=true" : "");
+        pw.print(" mAnimationType="); pw.println(animationTypeToString(mAnimationType));
         pw.print(prefix); pw.print("Animation: "); pw.println(mAnimation);
         if (mAnimation != null) {
             mAnimation.dump(pw, prefix + "  ");
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e0c473d..5f92bb6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3215,8 +3215,7 @@
         final boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter
                 && isChangingAppTransition();
 
-        // Delaying animation start isn't compatible with remote animations at all.
-        if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
+        if (controller != null) {
             // Here we load App XML in order to read com.android.R.styleable#Animation_showBackdrop.
             boolean showBackdrop = false;
             // Optionally set backdrop color if App explicitly provides it through
@@ -3639,20 +3638,6 @@
         return getAnimatingContainer(PARENTS, ANIMATION_TYPE_ALL);
     }
 
-    /**
-     * @see SurfaceAnimator#startDelayingAnimationStart
-     */
-    void startDelayingAnimationStart() {
-        mSurfaceAnimator.startDelayingAnimationStart();
-    }
-
-    /**
-     * @see SurfaceAnimator#endDelayingAnimationStart
-     */
-    void endDelayingAnimationStart() {
-        mSurfaceAnimator.endDelayingAnimationStart();
-    }
-
     @Override
     public int getSurfaceWidth() {
         return mSurfaceControl.getWidth();
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index cc5573b..f34ec72 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -19,6 +19,7 @@
 import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
@@ -71,6 +72,7 @@
     private static final int DEVICE_STATE_OPENED = 2;
     private static final int DEVICE_STATE_REAR_DISPLAY = 3;
     private static final int DEVICE_STATE_CONCURRENT_INNER_DEFAULT = 4;
+    private static final int DEVICE_STATE_REAR_DISPLAY_OUTER_DEFAULT = 5;
     private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90;
     private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125;
     private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
@@ -130,14 +132,17 @@
                             return hingeAngle >= MAX_CLOSED_ANGLE_DEGREES
                                     && hingeAngle <= TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES;
                         }),
-                createConfig(getOpenedDeviceState(), /* activeStatePredicate= */
-                        ALLOWED),
-                createConfig(getRearDisplayDeviceState(), /* activeStatePredicate= */
-                        NOT_ALLOWED),
-                createConfig(getDualDisplayDeviceState(), /* activeStatePredicate= */
-                        NOT_ALLOWED, /* availabilityPredicate= */
-                        provider -> !mIsDualDisplayBlockingEnabled
-                                || provider.hasNoConnectedExternalDisplay())};
+                createConfig(getOpenedDeviceState(),
+                        /* activeStatePredicate= */ ALLOWED),
+                createConfig(getRearDisplayDeviceState(),
+                        /* activeStatePredicate= */ NOT_ALLOWED),
+                createConfig(getDualDisplayDeviceState(),
+                        /* activeStatePredicate= */ NOT_ALLOWED,
+                        /* availabilityPredicate= */ provider -> !mIsDualDisplayBlockingEnabled
+                                || provider.hasNoConnectedExternalDisplay()),
+                createConfig(getRearDisplayOuterDefaultState(),
+                        /* activeStatePredicate= */ NOT_ALLOWED)
+        };
     }
 
     private DeviceStatePredicateWrapper createClosedConfiguration(
@@ -266,4 +271,24 @@
                 .setSystemProperties(systemProperties)
                 .build());
     }
+
+    /**
+     * Returns the {link DeviceState.Configuration} that represents the new rear display state
+     * where the inner display is also enabled, showing a system affordance to exit the state.
+     */
+    @NonNull
+    private DeviceState getRearDisplayOuterDefaultState() {
+        Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+                List.of(PROPERTY_EMULATED_ONLY,
+                        PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+                        PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST,
+                        PROPERTY_FEATURE_REAR_DISPLAY,
+                        PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT));
+
+        return new DeviceState(new DeviceState.Configuration.Builder(
+                DEVICE_STATE_REAR_DISPLAY_OUTER_DEFAULT,
+                "REAR_DISPLAY_OUTER_DEFAULT")
+                .setSystemProperties(systemProperties)
+                .build());
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9759772..19b0343 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1621,7 +1621,8 @@
             mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
             t.traceEnd();
 
-            if (!isWatch && android.app.supervision.flags.Flags.supervisionApi()) {
+            if (android.app.supervision.flags.Flags.supervisionApi()
+                    && (!isWatch || android.app.supervision.flags.Flags.supervisionApiOnWear())) {
                 t.traceBegin("StartSupervisionService");
                 mSystemServiceManager.startService(SupervisionService.Lifecycle.class);
                 t.traceEnd();
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
new file mode 100644
index 0000000..1be5cef
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.Flags.FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeNonSdkSandbox;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+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.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@AppModeFull
+@AppModeNonSdkSandbox
+@RunWith(AndroidJUnit4.class)
+public class BroadcastHelperTest {
+    private static final String TAG = "BroadcastHelperTest";
+    private static final String PACKAGE_CHANGED_TEST_PACKAGE_NAME = "testpackagename";
+    private static final String PACKAGE_CHANGED_TEST_MAIN_ACTIVITY =
+            PACKAGE_CHANGED_TEST_PACKAGE_NAME + ".MainActivity";
+    private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
+            "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Mock
+    ActivityManagerInternal mMockActivityManagerInternal;
+    @Mock
+    AndroidPackageInternal mMockAndroidPackageInternal;
+    @Mock
+    Computer mMockSnapshot;
+    @Mock
+    Handler mMockHandler;
+    @Mock
+    PackageManagerServiceInjector mMockPackageManagerServiceInjector;
+    @Mock
+    PackageMonitorCallbackHelper mMockPackageMonitorCallbackHelper;
+    @Mock
+    PackageStateInternal mMockPackageStateInternal;
+    @Mock
+    ParsedActivity mMockParsedActivity;
+    @Mock
+    UserManagerInternal mMockUserManagerInternal;
+
+    private Context mContext;
+    private BroadcastHelper mBroadcastHelper;
+
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+        when(mMockHandler.sendMessageAtTime(any(Message.class), anyLong())).thenAnswer(
+                i -> {
+                    ((Message) i.getArguments()[0]).getCallback().run();
+                    return true;
+                });
+        when(mMockPackageManagerServiceInjector.getActivityManagerInternal()).thenReturn(
+                mMockActivityManagerInternal);
+        when(mMockPackageManagerServiceInjector.getContext()).thenReturn(mContext);
+        when(mMockPackageManagerServiceInjector.getHandler()).thenReturn(mMockHandler);
+        when(mMockPackageManagerServiceInjector.getPackageMonitorCallbackHelper()).thenReturn(
+                mMockPackageMonitorCallbackHelper);
+        when(mMockPackageManagerServiceInjector.getUserManagerInternal()).thenReturn(
+                mMockUserManagerInternal);
+
+        mBroadcastHelper = new BroadcastHelper(mMockPackageManagerServiceInjector);
+    }
+
+    @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+    @Test
+    public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission()
+            throws Exception {
+        changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockActivityManagerInternal).broadcastIntentWithCallback(
+                captor.capture(), eq(null),
+                eq(new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}),
+                anyInt(), eq(null), eq(null), eq(null));
+        Intent intent = captor.getValue();
+        assertNotNull(intent);
+        assertThat(intent.getPackage()).isEqualTo("android");
+    }
+
+    @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
+    @Test
+    public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself()
+            throws Exception {
+        changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */);
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
+                eq(null), anyInt(), eq(null), eq(null), eq(null));
+        Intent intent = captor.getValue();
+        assertNotNull(intent);
+        assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void changeExportedComponent_sendPackageChangedBroadcastToAll() throws Exception {
+        changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */);
+
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
+                eq(null), anyInt(), eq(null), eq(null), eq(null));
+        Intent intent = captor.getValue();
+        assertNotNull(intent);
+        assertNull(intent.getPackage());
+    }
+
+    private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent) {
+        when(mMockSnapshot.getPackageStateInternal(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME),
+                anyInt())).thenReturn(mMockPackageStateInternal);
+        when(mMockSnapshot.isInstantAppInternal(any(), anyInt(), anyInt())).thenReturn(false);
+        when(mMockSnapshot.getVisibilityAllowLists(any(), any())).thenReturn(null);
+        when(mMockPackageStateInternal.getPkg()).thenReturn(mMockAndroidPackageInternal);
+
+        when(mMockParsedActivity.getClassName()).thenReturn(
+                PACKAGE_CHANGED_TEST_MAIN_ACTIVITY);
+        when(mMockParsedActivity.isExported()).thenReturn(changeExportedComponent);
+        ArrayList<ParsedActivity> parsedActivities = new ArrayList<>();
+        parsedActivities.add(mMockParsedActivity);
+
+        when(mMockAndroidPackageInternal.getReceivers()).thenReturn(new ArrayList<>());
+        when(mMockAndroidPackageInternal.getProviders()).thenReturn(new ArrayList<>());
+        when(mMockAndroidPackageInternal.getServices()).thenReturn(new ArrayList<>());
+        when(mMockAndroidPackageInternal.getActivities()).thenReturn(parsedActivities);
+
+        doNothing().when(mMockPackageMonitorCallbackHelper).notifyPackageChanged(any(),
+                anyBoolean(), any(), anyInt(), any(), any(), any(), any(), any());
+        when(mMockActivityManagerInternal.broadcastIntentWithCallback(any(), any(), any(), anyInt(),
+                any(), any(), any())).thenReturn(ActivityManager.BROADCAST_SUCCESS);
+
+        ArrayList<String> componentNames = new ArrayList<>();
+        componentNames.add(PACKAGE_CHANGED_TEST_MAIN_ACTIVITY);
+
+        mBroadcastHelper.sendPackageChangedBroadcast(mMockSnapshot,
+                PACKAGE_CHANGED_TEST_PACKAGE_NAME, true /* dontKillApp */, componentNames,
+                UserHandle.USER_SYSTEM, "test" /* reason */);
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 09d0e4a..5a59c57 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -201,7 +201,8 @@
             /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")),
             /* VerifierController */ mock(VerifierController::class.java),
             /* initialVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_OPEN,
-            /* currentVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED
+            /* currentVerificationPolicy */ VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
+            /* installDependencyHelper */ null
         )
     }
 
@@ -256,7 +257,8 @@
                                 mTmpDir,
                                 mock(PackageSessionProvider::class.java),
                                 mock(SilentUpdatePolicy::class.java),
-                                mock(VerifierController::class.java)
+                                mock(VerifierController::class.java),
+                                mock(InstallDependencyHelper::class.java)
                             )
                             ret.add(session)
                         } catch (e: Exception) {
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 979384c6..6acf242 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -109,6 +109,10 @@
     optimize: {
         enabled: false,
     },
+
+    data: [
+        ":HelloWorldUsingSdk1And2",
+    ],
 }
 
 java_library {
diff --git a/services/tests/mockingservicestests/AndroidTest.xml b/services/tests/mockingservicestests/AndroidTest.xml
index 7782d57..2b90119 100644
--- a/services/tests/mockingservicestests/AndroidTest.xml
+++ b/services/tests/mockingservicestests/AndroidTest.xml
@@ -23,6 +23,12 @@
         <option name="test-file-name" value="FrameworksMockingServicesTests.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true"/>
+        <option name="push-file" key="HelloWorldUsingSdk1And2.apk"
+            value="/data/local/tmp/tests/smockingservicestest/pm/HelloWorldUsingSdk1And2.apk"/>
+    </target_preparer>
+
     <option name="test-tag" value="FrameworksMockingServicesTests" />
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
new file mode 100644
index 0000000..f6c644e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.parsing.ApkLite;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.FileUtils;
+import android.os.OutcomeReceiver;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@RunWith(JUnit4.class)
+@RequiresFlagsEnabled(FLAG_SDK_DEPENDENCY_INSTALLER)
+public class InstallDependencyHelperTest {
+
+    @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+    @Rule public final CheckFlagsRule checkFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/";
+    private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+
+    @Mock private SharedLibrariesImpl mSharedLibraries;
+    private InstallDependencyHelper mInstallDependencyHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mInstallDependencyHelper = new InstallDependencyHelper(mSharedLibraries);
+    }
+
+    @Test
+    public void testResolveLibraryDependenciesIfNeeded_errorInSharedLibrariesImpl()
+            throws Exception {
+        doThrow(new PackageManagerException(new Exception("xyz")))
+                .when(mSharedLibraries).collectMissingSharedLibraryInfos(any());
+
+        PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+        CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        callback.assertFailure();
+
+        assertThat(callback.error).hasMessageThat().contains("xyz");
+    }
+
+    @Test
+    public void testResolveLibraryDependenciesIfNeeded_failsToBind() throws Exception {
+        // Return a non-empty list as missing dependency
+        PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+        List<SharedLibraryInfo> missingDependency = Collections.singletonList(
+                mock(SharedLibraryInfo.class));
+        when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+                .thenReturn(missingDependency);
+
+        CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        callback.assertFailure();
+
+        assertThat(callback.error).hasMessageThat().contains(
+                "Failed to bind to Dependency Installer");
+    }
+
+
+    @Test
+    public void testResolveLibraryDependenciesIfNeeded_allDependenciesInstalled() throws Exception {
+        // Return an empty list as missing dependency
+        PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
+        List<SharedLibraryInfo> missingDependency = Collections.emptyList();
+        when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
+                .thenReturn(missingDependency);
+
+        CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback);
+        callback.assertSuccess();
+    }
+
+    private static class CallbackHelper implements OutcomeReceiver<Void, PackageManagerException> {
+        public PackageManagerException error;
+
+        private final CountDownLatch mWait = new CountDownLatch(1);
+        private final boolean mExpectSuccess;
+
+        CallbackHelper(boolean expectSuccess) {
+            mExpectSuccess = expectSuccess;
+        }
+
+        @Override
+        public void onResult(Void result) {
+            if (!mExpectSuccess) {
+                fail("Expected to fail");
+            }
+            mWait.countDown();
+        }
+
+        @Override
+        public void onError(@NonNull PackageManagerException e) {
+            if (mExpectSuccess) {
+                fail("Expected success but received: " + e);
+            }
+            error = e;
+            mWait.countDown();
+        }
+
+        void assertSuccess() throws Exception {
+            assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(error).isNull();
+        }
+
+        void assertFailure() throws Exception {
+            assertThat(mWait.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(error).isNotNull();
+        }
+
+    }
+
+    private PackageLite getPackageLite(String apkFileName) throws Exception {
+        File apkFile = copyApkToTmpDir(TEST_APP_USING_SDK1_AND_SDK2);
+        ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
+                ParseTypeImpl.forDefaultParsing().reset(), apkFile, 0);
+        assertThat(result.isError()).isFalse();
+        ApkLite baseApk = result.getResult();
+
+        return new PackageLite(/*path=*/ null, baseApk.getPath(), baseApk,
+                /*splitNames=*/ null, /*isFeatureSplits=*/ null, /*usesSplitNames=*/ null,
+                /*configForSplit=*/ null, /*splitApkPaths=*/ null,
+                /*splitRevisionCodes=*/ null, baseApk.getTargetSdkVersion(),
+                /*requiredSplitTypes=*/ null, /*splitTypes=*/ null);
+    }
+
+    private File copyApkToTmpDir(String apkFileName) throws Exception {
+        File outFile = temporaryFolder.newFile(apkFileName);
+        String apkFilePath = PUSH_FILE_DIR + apkFileName;
+        File apkFile = new File(apkFilePath);
+        assertThat(apkFile.exists()).isTrue();
+        try (InputStream is = new FileInputStream(apkFile)) {
+            FileUtils.copyToFileOrThrow(is, outFile);
+        }
+        return outFile;
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index 591e8df..71c60ad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -742,10 +742,11 @@
                 /* stagedSessionErrorMessage */ "no error",
                 /* preVerifiedDomains */ null,
                 /* verifierController */ null,
-                /* initialVerificationPolicy */ 
+                /* initialVerificationPolicy */
                 PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
                 /* currentVerificationPolicy */
-                PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED);
+                PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED,
+                /* installDependencyHelper */ null);
 
         StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
         doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 411a610..361df94 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -44,7 +44,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 import com.android.server.UiServiceTestCase;
@@ -89,7 +89,7 @@
 import javax.annotation.Nullable;
 
 @RunWith(AndroidJUnit4.class)
-@EnableFlags({Flags.FLAG_VISIT_PERSON_URI, Flags.FLAG_API_RICH_ONGOING})
+@EnableFlags({Flags.FLAG_API_RICH_ONGOING})
 public class NotificationVisitUrisTest extends UiServiceTestCase {
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 9967cce..7dba142 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -21,7 +21,6 @@
 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.verifyZeroInteractions;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 
 import static org.junit.Assert.assertEquals;
@@ -165,31 +164,6 @@
     }
 
     @Test
-    public void testDelayingAnimationStart() {
-        mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
-                ANIMATION_TYPE_APP_TRANSITION);
-        verifyZeroInteractions(mSpec);
-        assertAnimating(mAnimatable);
-        assertTrue(mAnimatable.mSurfaceAnimator.isAnimationStartDelayed());
-        mAnimatable.mSurfaceAnimator.endDelayingAnimationStart();
-        verify(mSpec).startAnimation(any(), any(), eq(ANIMATION_TYPE_APP_TRANSITION), any());
-    }
-
-    @Test
-    public void testDelayingAnimationStartAndCancelled() {
-        mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
-        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
-                ANIMATION_TYPE_APP_TRANSITION);
-        mAnimatable.mSurfaceAnimator.cancelAnimation();
-        verifyZeroInteractions(mSpec);
-        assertNotAnimating(mAnimatable);
-        assertTrue(mAnimatable.mFinishedCallbackCalled);
-        assertEquals(ANIMATION_TYPE_APP_TRANSITION, mAnimatable.mFinishedAnimationType);
-        verify(mTransaction).remove(eq(mAnimatable.mLeash));
-    }
-
-    @Test
     public void testTransferAnimation() {
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
                 ANIMATION_TYPE_APP_TRANSITION);
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 9a9a331..ea61ad9 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -28,6 +28,7 @@
 import android.tools.traces.wm.WindowingMode
 import android.view.WindowInsets
 import android.view.WindowManager
+import android.window.DesktopModeFlags
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
@@ -35,7 +36,6 @@
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.MotionEventHelper.InputMethod.TOUCH
-import com.android.window.flags.Flags
 import java.time.Duration
 
 /**
@@ -107,7 +107,7 @@
 
         // drag the window to move to desktop
         if (motionEventHelper.inputMethod == TOUCH
-            && Flags.enableHoldToDragAppHandle()) {
+            && DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue) {
             // Touch requires hold-to-drag.
             motionEventHelper.holdToDrag(startX, startY, startX, endY, steps = 100)
         } else {
diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
index e2099e6..635e5de 100644
--- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
+++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
@@ -18,19 +18,27 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbManager;
+import android.hardware.usb.flags.Flags;
 import android.os.Binder;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.util.Log;
 
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -43,13 +51,36 @@
 
     private UsbManager mUsbManagerSys;
     private UsbManager mUsbManagerMock;
-    @Mock private android.hardware.usb.IUsbManager mMockUsbService;
+    @Mock
+    private android.hardware.usb.IUsbManager mMockUsbService;
+    private TestParcelFileDescriptor mTestParcelFileDescriptor = new TestParcelFileDescriptor(
+            new ParcelFileDescriptor(new FileDescriptor()));
+    @Mock
+    private UsbAccessory mMockUsbAccessory;
 
     /**
      * Counter for tracking UsbOperation operations.
      */
     private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
 
+    private class TestParcelFileDescriptor extends ParcelFileDescriptor {
+
+        private final AtomicInteger mCloseCount = new AtomicInteger();
+
+        TestParcelFileDescriptor(ParcelFileDescriptor wrapped) {
+            super(wrapped);
+        }
+
+        @Override
+        public void close() {
+            int unused = mCloseCount.incrementAndGet();
+        }
+
+        public void clearCloseCount() {
+            mCloseCount.set(0);
+        }
+    }
+
     public UsbManagerTestLib(Context context) {
         MockitoAnnotations.initMocks(this);
         mContext = context;
@@ -74,6 +105,34 @@
         mUsbManagerSys.setCurrentFunctions(functions);
     }
 
+    private InputStream openAccessoryInputStream(UsbAccessory accessory) {
+        try {
+            when(mMockUsbService.openAccessory(accessory)).thenReturn(mTestParcelFileDescriptor);
+        } catch (RemoteException remEx) {
+            Log.w(TAG, "RemoteException");
+        }
+
+        if (Flags.enableAccessoryStreamApi()) {
+            return mUsbManagerMock.openAccessoryInputStream(accessory);
+        }
+
+        throw new UnsupportedOperationException("Stream APIs not available");
+    }
+
+    private OutputStream openAccessoryOutputStream(UsbAccessory accessory) {
+        try {
+            when(mMockUsbService.openAccessory(accessory)).thenReturn(mTestParcelFileDescriptor);
+        } catch (RemoteException remEx) {
+            Log.w(TAG, "RemoteException");
+        }
+
+        if (Flags.enableAccessoryStreamApi()) {
+            return mUsbManagerMock.openAccessoryOutputStream(accessory);
+        }
+
+        throw new UnsupportedOperationException("Stream APIs not available");
+    }
+
     private void testSetGetCurrentFunctions_Matched(long functions) {
         setCurrentFunctions(functions);
         assertEquals("CurrentFunctions mismatched: ", functions, getCurrentFunctions());
@@ -94,7 +153,7 @@
         try {
             setCurrentFunctions(functions);
 
-            verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
+            verify(mMockUsbService).setCurrentFunctions(eq(functions), eq(operationId));
         } catch (RemoteException remEx) {
             Log.w(TAG, "RemoteException");
         }
@@ -118,7 +177,7 @@
         int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
         setCurrentFunctions(functions);
 
-        verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
+        verify(mMockUsbService).setCurrentFunctions(eq(functions), eq(operationId));
     }
 
     public void testGetCurrentFunctions_shouldMatched() {
@@ -138,4 +197,47 @@
         testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS);
         testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NCM);
     }
+
+    public void testParcelFileDescriptorClosedWhenAllOpenStreamsAreClosed() {
+        mTestParcelFileDescriptor.clearCloseCount();
+        try {
+            try (InputStream ignored = openAccessoryInputStream(mMockUsbAccessory)) {
+                //noinspection EmptyTryBlock
+                try (OutputStream ignored2 = openAccessoryOutputStream(mMockUsbAccessory)) {
+                    // do nothing
+                }
+            }
+
+            // ParcelFileDescriptor is closed only once.
+            assertEquals(mTestParcelFileDescriptor.mCloseCount.get(), 1);
+            mTestParcelFileDescriptor.clearCloseCount();
+        } catch (IOException e) {
+            // do nothing
+        }
+    }
+
+    public void testOnlyOneOpenInputStreamAllowed() {
+        try {
+            //noinspection EmptyTryBlock
+            try (InputStream ignored = openAccessoryInputStream(mMockUsbAccessory)) {
+                assertThrows(IllegalStateException.class,
+                        () -> openAccessoryInputStream(mMockUsbAccessory));
+            }
+        } catch (IOException e) {
+            // do nothing
+        }
+    }
+
+    public void testOnlyOneOpenOutputStreamAllowed() {
+        try {
+            //noinspection EmptyTryBlock
+            try (OutputStream ignored = openAccessoryOutputStream(mMockUsbAccessory)) {
+                assertThrows(IllegalStateException.class,
+                        () -> openAccessoryOutputStream(mMockUsbAccessory));
+            }
+        } catch (IOException e) {
+            // do nothing
+        }
+    }
+
 }
diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
index 8b21763..40fd0b4 100644
--- a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
+++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
@@ -18,17 +18,21 @@
 
 import android.content.Context;
 import android.hardware.usb.UsbManager;
+import android.hardware.usb.flags.Flags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.Ignore;
+import com.android.server.usblib.UsbManagerTestLib;
+
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import com.android.server.usblib.UsbManagerTestLib;
-
 /**
  * Unit tests for {@link android.hardware.usb.UsbManager}.
  * Note: MUST claimed MANAGE_USB permission in Manifest
@@ -41,6 +45,9 @@
     private final UsbManagerTestLib mUsbManagerTestLib =
             new UsbManagerTestLib(mContext = InstrumentationRegistry.getContext());
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
     /**
      * Verify NO SecurityException
      * Go through System Server
@@ -92,4 +99,23 @@
     public void testUsbApi_SetCurrentFunctions_shouldMatched() {
         mUsbManagerTestLib.testSetCurrentFunctions_shouldMatched();
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+    public void testUsbApi_closesParcelFileDescriptorAfterAllStreamsClosed() {
+        mUsbManagerTestLib.testParcelFileDescriptorClosedWhenAllOpenStreamsAreClosed();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+    public void testUsbApi_callingOpenAccessoryInputStreamTwiceThrowsException() {
+        mUsbManagerTestLib.testOnlyOneOpenInputStreamAllowed();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API)
+    public void testUsbApi_callingOpenAccessoryOutputStreamTwiceThrowsException() {
+        mUsbManagerTestLib.testOnlyOneOpenOutputStreamAllowed();
+    }
+
 }
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index ac96ef2..be5c84c 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -53,7 +53,6 @@
     private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
     private static final Field MESSAGE_NEXT_FIELD;
     private static final Field MESSAGE_WHEN_FIELD;
-    private static Field MESSAGE_QUEUE_USE_CONCURRENT_FIELD = null;
 
     private Looper mLooper;
     private MessageQueue mQueue;
@@ -64,14 +63,6 @@
 
     static {
         try {
-            MESSAGE_QUEUE_USE_CONCURRENT_FIELD =
-                    MessageQueue.class.getDeclaredField("mUseConcurrent");
-            MESSAGE_QUEUE_USE_CONCURRENT_FIELD.setAccessible(true);
-        } catch (NoSuchFieldException ignored) {
-            // Ignore - maybe this is not CombinedMessageQueue?
-        }
-
-        try {
             MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
             MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
             MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
@@ -155,15 +146,6 @@
         mLooper = l;
         mQueue = mLooper.getQueue();
         mHandler = new Handler(mLooper);
-
-        // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
-        if (MESSAGE_QUEUE_USE_CONCURRENT_FIELD != null) {
-            try {
-                MESSAGE_QUEUE_USE_CONCURRENT_FIELD.set(mQueue, false);
-            } catch (IllegalAccessException e) {
-                throw new RuntimeException(e);
-            }
-        }
     }
 
     /**
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 1bcfaf6..56b0a25 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -100,18 +100,6 @@
             throw new RuntimeException("Reflection error constructing or accessing looper", e);
         }
 
-        // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
-        try {
-            Field messageQueueUseConcurrentField =
-                    MessageQueue.class.getDeclaredField("mUseConcurrent");
-            messageQueueUseConcurrentField.setAccessible(true);
-            messageQueueUseConcurrentField.set(mLooper.getQueue(), false);
-        } catch (NoSuchFieldException e) {
-            // Ignore - maybe this is not CombinedMessageQueue?
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException("Reflection error constructing or accessing looper", e);
-        }
-
         mClock = clock;
     }