Merge "NSSL: Smooth transition from shade->lockscreen" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 1fb5f34..904109b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -68,6 +68,7 @@
     ":android.tracing.flags-aconfig-java{.generated_srcjars}",
     ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
     ":android.webkit.flags-aconfig-java{.generated_srcjars}",
+    ":android.provider.flags-aconfig-java{.generated_srcjars}",
 ]
 
 filegroup {
@@ -428,7 +429,10 @@
 aconfig_declarations {
     name: "com.android.media.flags.bettertogether-aconfig",
     package: "com.android.media.flags",
-    srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
+    srcs: [
+        "media/java/android/media/flags/media_better_together.aconfig",
+        "media/java/android/media/flags/fade_manager_configuration.aconfig",
+    ],
 }
 
 java_aconfig_library {
@@ -845,3 +849,16 @@
     aconfig_declarations: "android.webkit.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Provider
+aconfig_declarations {
+    name: "android.provider.flags-aconfig",
+    package: "android.provider",
+    srcs: ["core/java/android/provider/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.provider.flags-aconfig-java",
+    aconfig_declarations: "android.provider.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 72322ef5..548fa2f 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -8924,9 +8924,9 @@
 android.view.accessibility.IAccessibilityManagerClient$Stub$Proxy
 android.view.accessibility.IAccessibilityManagerClient$Stub
 android.view.accessibility.IAccessibilityManagerClient
-android.view.accessibility.IWindowMagnificationConnection$Stub$Proxy
-android.view.accessibility.IWindowMagnificationConnection$Stub
-android.view.accessibility.IWindowMagnificationConnection
+android.view.accessibility.IMagnificationConnection$Stub$Proxy
+android.view.accessibility.IMagnificationConnection$Stub
+android.view.accessibility.IMagnificationConnection
 android.view.accessibility.WeakSparseArray$WeakReferenceWithId
 android.view.accessibility.WeakSparseArray
 android.view.animation.AccelerateDecelerateInterpolator
diff --git a/config/preloaded-classes b/config/preloaded-classes
index cace87c..c49971e 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -8955,9 +8955,9 @@
 android.view.accessibility.IAccessibilityManagerClient$Stub$Proxy
 android.view.accessibility.IAccessibilityManagerClient$Stub
 android.view.accessibility.IAccessibilityManagerClient
-android.view.accessibility.IWindowMagnificationConnection$Stub$Proxy
-android.view.accessibility.IWindowMagnificationConnection$Stub
-android.view.accessibility.IWindowMagnificationConnection
+android.view.accessibility.IMagnificationConnection$Stub$Proxy
+android.view.accessibility.IMagnificationConnection$Stub
+android.view.accessibility.IMagnificationConnection
 android.view.accessibility.WeakSparseArray$WeakReferenceWithId
 android.view.accessibility.WeakSparseArray
 android.view.animation.AccelerateDecelerateInterpolator
diff --git a/core/api/current.txt b/core/api/current.txt
index 095da88..1379821 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12428,7 +12428,7 @@
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
     method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender);
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender);
@@ -12853,6 +12853,8 @@
     field public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4; // 0x4
     field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
     field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
+    field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_ARCHIVE = 16; // 0x10
+    field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_SHOW_DIALOG = 32; // 0x20
     field public static final int DONT_KILL_APP = 1; // 0x1
     field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
     field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
@@ -19508,6 +19510,7 @@
     field @Deprecated @NonNull public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_RADIAL_DISTORTION;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_STATE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.String> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> NOISE_REDUCTION_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
@@ -19533,6 +19536,7 @@
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_FACE_DETECT_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Point[]> STATISTICS_HOT_PIXEL_MAP;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> STATISTICS_HOT_PIXEL_MAP_MODE;
+    field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.LensIntrinsicsSample[]> STATISTICS_LENS_INTRINSICS_SAMPLES;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.LensShadingMap> STATISTICS_LENS_SHADING_CORRECTION_MAP;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_LENS_SHADING_MAP_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_OIS_DATA_MODE;
@@ -19689,6 +19693,12 @@
     method public boolean isMultiResolution();
   }
 
+  @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class LensIntrinsicsSample {
+    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public LensIntrinsicsSample(long, @NonNull float[]);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public float[] getLensIntrinsics();
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getTimestamp();
+  }
+
   public final class LensShadingMap {
     method public void copyGainFactors(float[], int);
     method public int getColumnCount();
@@ -40620,7 +40630,6 @@
     method public static boolean isValidId(android.net.Uri, String);
     method public static android.net.Uri.Builder newId(android.content.Context);
     method public static String relevanceToString(int);
-    method @FlaggedApi("android.app.modes_api") @NonNull public static String sourceToString(int);
     method public static String stateToString(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Condition> CREATOR;
@@ -49259,6 +49268,7 @@
     field public static final int DENSITY_300 = 300; // 0x12c
     field public static final int DENSITY_340 = 340; // 0x154
     field public static final int DENSITY_360 = 360; // 0x168
+    field @FlaggedApi("com.android.window.flags.density_390_api") public static final int DENSITY_390 = 390; // 0x186
     field public static final int DENSITY_400 = 400; // 0x190
     field public static final int DENSITY_420 = 420; // 0x1a4
     field public static final int DENSITY_440 = 440; // 0x1b8
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 12b1f6a..b58c822 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -8,16 +8,6 @@
     method @Deprecated public void setLatestEventInfo(android.content.Context, CharSequence, CharSequence, android.app.PendingIntent);
   }
 
-  public static final class Notification.BubbleMetadata implements android.os.Parcelable {
-    method @Deprecated @Nullable public android.graphics.drawable.Icon getBubbleIcon();
-    method @Deprecated @Nullable public android.app.PendingIntent getBubbleIntent();
-  }
-
-  public static final class Notification.BubbleMetadata.Builder {
-    method @Deprecated @NonNull public android.app.Notification.BubbleMetadata.Builder createIntentBubble(@NonNull android.app.PendingIntent, @NonNull android.graphics.drawable.Icon);
-    method @Deprecated @NonNull public android.app.Notification.BubbleMetadata.Builder createShortcutBubble(@NonNull String);
-  }
-
   public static class Notification.Builder {
     method @Deprecated public android.app.Notification.Builder setChannel(String);
     method @Deprecated public android.app.Notification.Builder setTimeout(long);
@@ -111,28 +101,6 @@
     field @Deprecated public static final int MATRIX_SAVE_FLAG = 1; // 0x1
   }
 
-  public final class ImageDecoder implements java.lang.AutoCloseable {
-    method @Deprecated public boolean getAsAlphaMask();
-    method @Deprecated public boolean getConserveMemory();
-    method @Deprecated public boolean getDecodeAsAlphaMask();
-    method @Deprecated public boolean getMutable();
-    method @Deprecated public boolean getRequireUnpremultiplied();
-    method @Deprecated public android.graphics.ImageDecoder setAsAlphaMask(boolean);
-    method @Deprecated public void setConserveMemory(boolean);
-    method @Deprecated public android.graphics.ImageDecoder setDecodeAsAlphaMask(boolean);
-    method @Deprecated public android.graphics.ImageDecoder setMutable(boolean);
-    method @Deprecated public android.graphics.ImageDecoder setRequireUnpremultiplied(boolean);
-    method @Deprecated public android.graphics.ImageDecoder setResize(int, int);
-    method @Deprecated public android.graphics.ImageDecoder setResize(int);
-    field @Deprecated public static final int ERROR_SOURCE_ERROR = 3; // 0x3
-    field @Deprecated public static final int ERROR_SOURCE_EXCEPTION = 1; // 0x1
-    field @Deprecated public static final int ERROR_SOURCE_INCOMPLETE = 2; // 0x2
-  }
-
-  @Deprecated public static class ImageDecoder.IncompleteException extends java.io.IOException {
-    ctor public ImageDecoder.IncompleteException();
-  }
-
   @Deprecated public class LayerRasterizer extends android.graphics.Rasterizer {
     ctor public LayerRasterizer();
     method public void addLayer(android.graphics.Paint, float, float);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 847edd1..e0dfd39 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -351,6 +351,7 @@
     field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
     field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT";
+    field @FlaggedApi("android.service.chooser.support_nfc_resolver") public static final String SHOW_CUSTOMIZED_RESOLVER = "android.permission.SHOW_CUSTOMIZED_RESOLVER";
     field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
     field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
@@ -3872,6 +3873,7 @@
     field public static final int DATA_LOADER_TYPE_STREAMING = 1; // 0x1
     field public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
     field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
+    field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_DELETE_FLAGS = "android.content.pm.extra.DELETE_FLAGS";
     field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
     field @Deprecated public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
     field public static final int LOCATION_DATA_APP = 0; // 0x0
@@ -11260,8 +11262,8 @@
 
   public static final class Settings.System extends android.provider.Settings.NameValueTable {
     method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean, boolean);
-    method public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
+    method @FlaggedApi("android.provider.system_settings_default") @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean, boolean);
+    method @FlaggedApi("android.provider.system_settings_default") public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
   }
 
   public static final class SimPhonebookContract.SimRecords {
@@ -11782,6 +11784,14 @@
 
 }
 
+package android.service.chooser {
+
+  @FlaggedApi("android.service.chooser.support_nfc_resolver") public class CustomChoosers {
+    method @FlaggedApi("android.service.chooser.support_nfc_resolver") @NonNull public static android.content.Intent createNfcResolverIntent(@NonNull android.content.Intent, @Nullable CharSequence, @NonNull java.util.List<android.content.pm.ResolveInfo>);
+  }
+
+}
+
 package android.service.cloudsearch {
 
   public abstract class CloudSearchService extends android.app.Service {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f4c8429..71a05a9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3101,6 +3101,10 @@
 
 package android.speech {
 
+  public abstract class RecognitionService extends android.app.Service {
+    method public void onBindInternal();
+  }
+
   public class SpeechRecognizer {
     method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceTestingSpeechRecognizer(@NonNull android.content.Context);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SPEECH_RECOGNITION) public void setTemporaryOnDeviceRecognizer(@Nullable android.content.ComponentName);
diff --git a/core/java/android/accessibilityservice/AccessibilityTrace.java b/core/java/android/accessibilityservice/AccessibilityTrace.java
index f28015a..7700b33 100644
--- a/core/java/android/accessibilityservice/AccessibilityTrace.java
+++ b/core/java/android/accessibilityservice/AccessibilityTrace.java
@@ -35,7 +35,7 @@
     String NAME_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK =
             "IAccessibilityInteractionConnectionCallback";
     String NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = "IRemoteMagnificationAnimationCallback";
-    String NAME_WINDOW_MAGNIFICATION_CONNECTION = "IWindowMagnificationConnection";
+    String NAME_MAGNIFICATION_CONNECTION = "IMagnificationConnection";
     String NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = "IWindowMagnificationConnectionCallback";
     String NAME_WINDOW_MANAGER_INTERNAL = "WindowManagerInternal";
     String NAME_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = "WindowsForAccessibilityCallback";
@@ -58,7 +58,7 @@
     long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION = 0x0000000000000010L;
     long FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK = 0x0000000000000020L;
     long FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK = 0x0000000000000040L;
-    long FLAGS_WINDOW_MAGNIFICATION_CONNECTION = 0x0000000000000080L;
+    long FLAGS_MAGNIFICATION_CONNECTION = 0x0000000000000080L;
     long FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK = 0x0000000000000100L;
     long FLAGS_WINDOW_MANAGER_INTERNAL = 0x0000000000000200L;
     long FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK = 0x0000000000000400L;
@@ -98,7 +98,7 @@
                     NAME_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK,
                     FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK),
             new AbstractMap.SimpleEntry<String, Long>(
-                    NAME_WINDOW_MAGNIFICATION_CONNECTION, FLAGS_WINDOW_MAGNIFICATION_CONNECTION),
+                    NAME_MAGNIFICATION_CONNECTION, FLAGS_MAGNIFICATION_CONNECTION),
             new AbstractMap.SimpleEntry<String, Long>(
                     NAME_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
                     FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK),
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index aec0427..71fe47e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -8370,9 +8370,29 @@
      * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}.
      * @hide
      */
+    public int unsafeCheckOpRawNoThrow(int op, @NonNull AttributionSource attributionSource) {
+        return unsafeCheckOpRawNoThrow(op, attributionSource.getUid(),
+                attributionSource.getPackageName(), attributionSource.getDeviceId());
+    }
+
+    /**
+     * Returns the <em>raw</em> mode associated with the op.
+     * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}.
+     * @hide
+     */
     public int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName) {
+        return unsafeCheckOpRawNoThrow(op, uid, packageName, Context.DEVICE_ID_DEFAULT);
+    }
+
+    private int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName,
+            int virtualDeviceId) {
         try {
-            return mService.checkOperationRaw(op, uid, packageName, null);
+            if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+                return mService.checkOperationRaw(op, uid, packageName, null);
+            } else {
+                return mService.checkOperationRawForDevice(op, uid, packageName, null,
+                        Context.DEVICE_ID_DEFAULT);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -8517,12 +8537,29 @@
     }
 
     /**
+     * @see #noteOp(String, int, String, String, String)
+     *
+     * @hide
+     */
+    public int noteOpNoThrow(int op, @NonNull AttributionSource attributionSource,
+            @Nullable String message) {
+        return noteOpNoThrow(op, attributionSource.getUid(), attributionSource.getPackageName(),
+                attributionSource.getAttributionTag(), attributionSource.getDeviceId(), message);
+    }
+
+    /**
      * @see #noteOpNoThrow(String, int, String, String, String)
      *
      * @hide
      */
     public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
             @Nullable String attributionTag, @Nullable String message) {
+        return noteOpNoThrow(op, uid, packageName, attributionTag, Context.DEVICE_ID_DEFAULT,
+                message);
+    }
+
+    private int noteOpNoThrow(int op, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, int virtualDeviceId, @Nullable String message) {
         try {
             collectNoteOpCallsForValidation(op);
             int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
@@ -8535,9 +8572,15 @@
                 }
             }
 
-            SyncNotedAppOp syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
+            SyncNotedAppOp syncOp;
+            if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+                syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
                     collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
-
+            } else {
+                syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
+                    virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
+                    shouldCollectMessage);
+            }
             if (syncOp.getOpMode() == MODE_ALLOWED) {
                 if (collectionMode == COLLECT_SELF) {
                     collectNotedOpForSelf(syncOp);
@@ -8775,7 +8818,8 @@
     @UnsupportedAppUsage
     public int checkOp(int op, int uid, String packageName) {
         try {
-            int mode = mService.checkOperation(op, uid, packageName);
+            int mode = mService.checkOperationForDevice(op, uid, packageName,
+                Context.DEVICE_ID_DEFAULT);
             if (mode == MODE_ERRORED) {
                 throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
             }
@@ -8786,6 +8830,19 @@
     }
 
     /**
+     * Like {@link #checkOp} but instead of throwing a {@link SecurityException}, it
+     * returns {@link #MODE_ERRORED}.
+     *
+     * @see #checkOp(int, int, String)
+     *
+     * @hide
+     */
+    public int checkOpNoThrow(int op, AttributionSource attributionSource) {
+        return checkOpNoThrow(op, attributionSource.getUid(), attributionSource.getPackageName(),
+                attributionSource.getDeviceId());
+    }
+
+    /**
      * Like {@link #checkOp} but instead of throwing a {@link SecurityException} it
      * returns {@link #MODE_ERRORED}.
      *
@@ -8795,8 +8852,18 @@
      */
     @UnsupportedAppUsage
     public int checkOpNoThrow(int op, int uid, String packageName) {
+        return checkOpNoThrow(op, uid, packageName, Context.DEVICE_ID_DEFAULT);
+    }
+
+    private int checkOpNoThrow(int op, int uid, String packageName, int virtualDeviceId) {
         try {
-            int mode = mService.checkOperation(op, uid, packageName);
+            int mode;
+            if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+                mode = mService.checkOperation(op, uid, packageName);
+            } else {
+                mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId);
+            }
+
             return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -9026,9 +9093,32 @@
      *
      * @hide
      */
+    public int startOpNoThrow(@NonNull IBinder token, int op,
+            @NonNull AttributionSource attributionSource,
+            boolean startIfModeDefault, @Nullable String message,
+            @AttributionFlags int attributionFlags, int attributionChainId) {
+        return startOpNoThrow(token, op, attributionSource.getUid(),
+                attributionSource.getPackageName(), startIfModeDefault,
+                attributionSource.getAttributionTag(), attributionSource.getDeviceId(),
+                message, attributionFlags, attributionChainId);
+    }
+
+    /**
+     * @see #startOpNoThrow(String, int, String, String, String)
+     *
+     * @hide
+     */
     public int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName,
             boolean startIfModeDefault, @Nullable String attributionTag, @Nullable String message,
             @AttributionFlags int attributionFlags, int attributionChainId) {
+        return startOpNoThrow(token, op, uid, packageName, startIfModeDefault, attributionTag,
+                Context.DEVICE_ID_DEFAULT, message, attributionFlags, attributionChainId);
+    }
+
+    private int startOpNoThrow(@NonNull IBinder token, int op, int uid, @NonNull String packageName,
+            boolean startIfModeDefault, @Nullable String attributionTag, int virtualDeviceId,
+            @Nullable String message, @AttributionFlags int attributionFlags,
+            int attributionChainId) {
         try {
             collectNoteOpCallsForValidation(op);
             int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
@@ -9041,10 +9131,17 @@
                 }
             }
 
-            SyncNotedAppOp syncOp = mService.startOperation(token, op, uid, packageName,
+            SyncNotedAppOp syncOp;
+            if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+                syncOp = mService.startOperation(token, op, uid, packageName,
                     attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message,
                     shouldCollectMessage, attributionFlags, attributionChainId);
-
+            } else {
+                syncOp = mService.startOperationForDevice(token, op, uid, packageName,
+                    attributionTag, virtualDeviceId, startIfModeDefault,
+                    collectionMode == COLLECT_ASYNC, message, shouldCollectMessage,
+                    attributionFlags, attributionChainId);
+            }
             if (syncOp.getOpMode() == MODE_ALLOWED) {
                 if (collectionMode == COLLECT_SELF) {
                     collectNotedOpForSelf(syncOp);
@@ -9252,10 +9349,31 @@
      *
      * @hide
      */
+    public void finishOp(IBinder token, int op, @NonNull AttributionSource attributionSource) {
+        finishOp(token, op, attributionSource.getUid(),
+                attributionSource.getPackageName(), attributionSource.getAttributionTag(),
+                attributionSource.getDeviceId());
+    }
+
+    /**
+     * @see #finishOp(String, int, String, String)
+     *
+     * @hide
+     */
     public void finishOp(IBinder token, int op, int uid, @NonNull String packageName,
             @Nullable String attributionTag) {
+        finishOp(token, op, uid, packageName, attributionTag, Context.DEVICE_ID_DEFAULT);
+    }
+
+    private void finishOp(IBinder token, int op, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int virtualDeviceId ) {
         try {
-            mService.finishOperation(token, op, uid, packageName, attributionTag);
+            if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
+                mService.finishOperation(token, op, uid, packageName, attributionTag);
+            } else {
+                mService.finishOperationForDevice(token, op, uid, packageName, attributionTag,
+                    virtualDeviceId);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index 43023fe..8daee58 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -26,11 +26,11 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.util.function.HeptFunction;
+import com.android.internal.util.function.DodecFunction;
+import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
+import com.android.internal.util.function.OctFunction;
 import com.android.internal.util.function.QuadFunction;
-import com.android.internal.util.function.QuintConsumer;
-import com.android.internal.util.function.QuintFunction;
 import com.android.internal.util.function.UndecFunction;
 
 /**
@@ -48,13 +48,14 @@
          * @param uid The UID for which to check.
          * @param packageName The package for which to check.
          * @param attributionTag The attribution tag for which to check.
+         * @param virtualDeviceId the device for which to check the op
          * @param raw Whether to check the raw op i.e. not interpret the mode based on UID state.
          * @param superImpl The super implementation.
          * @return The app op check result.
          */
         int checkOperation(int code, int uid, String packageName, @Nullable String attributionTag,
-                boolean raw, QuintFunction<Integer, Integer, String, String, Boolean, Integer>
-                superImpl);
+                int virtualDeviceId, boolean raw, HexFunction<Integer, Integer, String, String,
+                Integer, Boolean, Integer> superImpl);
 
         /**
          * Allows overriding check audio operation behavior.
@@ -76,16 +77,17 @@
          * @param uid The UID for which to note.
          * @param packageName The package for which to note. {@code null} for system package.
          * @param featureId Id of the feature in the package
+         * @param virtualDeviceId the device for which to note the op
          * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected
          * @param message The message in the async noted op
          * @param superImpl The super implementation.
          * @return The app op note result.
          */
         SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
-                @Nullable String featureId, boolean shouldCollectAsyncNotedOp,
+                @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
-                @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean,
-                        SyncNotedAppOp> superImpl);
+                @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
+                        Boolean, SyncNotedAppOp> superImpl);
 
         /**
          * Allows overriding note proxy operation behavior.
@@ -113,6 +115,7 @@
          * @param uid The UID for which to note.
          * @param packageName The package for which to note. {@code null} for system package.
          * @param attributionTag the attribution tag.
+         * @param virtualDeviceId the device for which to start the op
          * @param startIfModeDefault Whether to start the op of the mode is default.
          * @param shouldCollectAsyncNotedOp If an {@link AsyncNotedAppOp} should be collected
          * @param message The message in the async noted op
@@ -123,11 +126,11 @@
          * @return The app op note result.
          */
         SyncNotedAppOp startOperation(IBinder token, int code, int uid,
-                @Nullable String packageName, @Nullable String attributionTag,
+                @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId,
                 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
                 @AttributionFlags int attributionFlags, int attributionChainId,
-                @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean,
+                @NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean,
                         Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl);
 
         /**
@@ -164,11 +167,13 @@
          * @param uid The UID for which the op was noted.
          * @param packageName The package for which it was noted. {@code null} for system package.
          * @param attributionTag the attribution tag.
+         * @param virtualDeviceId the device for which to finish the op
+         * @param superImpl
          */
         default void finishOperation(IBinder clientId, int code, int uid, String packageName,
-                String attributionTag,
-                @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl) {
-            superImpl.accept(clientId, code, uid, packageName, attributionTag);
+                String attributionTag, int virtualDeviceId, @NonNull HexConsumer<IBinder, Integer,
+                        Integer, String, String, Integer> superImpl) {
+            superImpl.accept(clientId, code, uid, packageName, attributionTag, virtualDeviceId);
         }
 
         /**
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index fd13174..b781ce5 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1112,12 +1112,17 @@
      * ready to execute it and connectivity is available.
      *
      * @param request the parameters specifying this download
-     * @return an ID for the download, unique across the system.  This ID is used to make future
-     * calls related to this download.
+     * @return an ID for the download, unique across the system.  This ID is used to make
+     * future calls related to this download. Returns -1 if the operation fails.
      */
     public long enqueue(Request request) {
         ContentValues values = request.toContentValues(mPackageName);
         Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
+        if (downloadUri == null) {
+            // If insert fails due to RemoteException, it would return a null uri.
+            return -1;
+        }
+
         long id = Long.parseLong(downloadUri.getLastPathSegment());
         return id;
     }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8c5773a..c003540 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -10383,16 +10383,6 @@
         }
 
         /**
-         * @deprecated use {@link #getIntent()} instead.
-         * @removed Removed from the R SDK but was never publicly stable.
-         */
-        @Nullable
-        @Deprecated
-        public PendingIntent getBubbleIntent() {
-            return mPendingIntent;
-        }
-
-        /**
          * @return the pending intent to send when the bubble is dismissed by a user, if one exists.
          */
         @Nullable
@@ -10411,16 +10401,6 @@
         }
 
         /**
-         * @deprecated use {@link #getIcon()} instead.
-         * @removed Removed from the R SDK but was never publicly stable.
-         */
-        @Nullable
-        @Deprecated
-        public Icon getBubbleIcon() {
-            return mIcon;
-        }
-
-        /**
          * @return the ideal height, in DPs, for the floating window that app content defined by
          * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has
          * not been set.
@@ -10677,48 +10657,6 @@
             }
 
             /**
-             * @deprecated use {@link Builder#Builder(String)} instead.
-             * @removed Removed from the R SDK but was never publicly stable.
-             */
-            @NonNull
-            @Deprecated
-            public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) {
-                if (!TextUtils.isEmpty(shortcutId)) {
-                    // If shortcut id is set, we don't use these if they were previously set.
-                    mPendingIntent = null;
-                    mIcon = null;
-                }
-                mShortcutId = shortcutId;
-                return this;
-            }
-
-            /**
-             * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead.
-             * @removed Removed from the R SDK but was never publicly stable.
-             */
-            @NonNull
-            @Deprecated
-            public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent,
-                    @NonNull Icon icon) {
-                if (intent == null) {
-                    throw new IllegalArgumentException("Bubble requires non-null pending intent");
-                }
-                if (icon == null) {
-                    throw new IllegalArgumentException("Bubbles require non-null icon");
-                }
-                if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP
-                        && icon.getType() != TYPE_URI) {
-                    Log.w(TAG, "Bubbles work best with icons of TYPE_URI or "
-                            + "TYPE_URI_ADAPTIVE_BITMAP. "
-                            + "In the future, using an icon of this type will be required.");
-                }
-                mShortcutId = null;
-                mPendingIntent = intent;
-                mIcon = icon;
-                return this;
-            }
-
-            /**
              * Sets the intent for the bubble.
              *
              * <p>The intent that will be used when the bubble is expanded. This will display the
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 697c25c..b2074a6 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -107,6 +107,13 @@
     }
 
     /** @hide */
+    public AttributionSource(int uid, @Nullable String packageName,
+            @Nullable String attributionTag, int virtualDeviceId) {
+        this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken, null,
+                virtualDeviceId, null);
+    }
+
+    /** @hide */
     public AttributionSource(int uid, int pid, @Nullable String packageName,
             @Nullable String attributionTag) {
         this(uid, pid, packageName, attributionTag, sDefaultToken);
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 1f25fd0..32ecb58 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -80,7 +80,7 @@
             long timeout);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
-    void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+    void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle, int flags);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
     void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d35c392..457fd63 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -323,6 +323,14 @@
      */
     @SystemApi
     public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
+    /**
+     * Key for passing extra delete flags during archiving.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+    public static final String EXTRA_DELETE_FLAGS = "android.content.pm.extra.DELETE_FLAGS";
 
     /**
      * Type of DataLoader for this session. Will be one of
@@ -2330,6 +2338,7 @@
      * communicated.
      *
      * @param statusReceiver Callback used to notify when the operation is completed.
+     * @param flags Flags for archiving. Can be 0 or {@link PackageManager#DELETE_SHOW_DIALOG}.
      * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
      *                                              available to the caller or isn't archived.
      */
@@ -2337,11 +2346,12 @@
             Manifest.permission.DELETE_PACKAGES,
             Manifest.permission.REQUEST_DELETE_PACKAGES})
     @FlaggedApi(Flags.FLAG_ARCHIVING)
-    public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
+    public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver,
+            @DeleteFlags int flags)
             throws PackageManager.NameNotFoundException {
         try {
             mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver,
-                    new UserHandle(mUserId));
+                    new UserHandle(mUserId), flags);
         } catch (ParcelableException e) {
             e.maybeRethrow(PackageManager.NameNotFoundException.class);
         } catch (RemoteException e) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a22fe3f..7bb673a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2552,6 +2552,7 @@
             DELETE_SYSTEM_APP,
             DELETE_DONT_KILL_APP,
             DELETE_CHATTY,
+            DELETE_SHOW_DIALOG,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DeleteFlags {}
@@ -2595,15 +2596,21 @@
     public static final int DELETE_DONT_KILL_APP = 0x00000008;
 
     /**
-     * Flag parameter for {@link #deletePackage} to indicate that the deletion is an archival. This
+     * Flag parameter for {@link PackageInstaller#uninstall(VersionedPackage, int, IntentSender)} to
+     * indicate that the deletion is an archival. This
      * flag is only for internal usage as part of
-     * {@link PackageInstaller#requestArchive(String, IntentSender)}.
-     *
-     * @hide
+     * {@link PackageInstaller#requestArchive}.
      */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
     public static final int DELETE_ARCHIVE = 0x00000010;
 
     /**
+     * Show a confirmation dialog to the user when app is being deleted.
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+    public static final int DELETE_SHOW_DIALOG = 0x00000020;
+
+    /**
      * Flag parameter for {@link #deletePackage} to indicate that package deletion
      * should be chatty.
      *
@@ -8964,7 +8971,7 @@
      * Returns true if an app is archivable.
      *
      * @throws NameNotFoundException if the given package name is not available to the caller.
-     * @see PackageInstaller#requestArchive(String, IntentSender)
+     * @see PackageInstaller#requestArchive
      */
     @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
     public boolean isAppArchivable(@NonNull String packageName) throws NameNotFoundException {
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 12ab0f6..35f295a 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5226,6 +5226,60 @@
             new Key<android.hardware.camera2.params.OisSample[]>("android.statistics.oisSamples", android.hardware.camera2.params.OisSample[].class);
 
     /**
+     * <p>An array of intra-frame lens intrinsic samples.</p>
+     * <p>Contains an array of intra-frame {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration} updates. This must
+     * not be confused or compared to {@link CaptureResult#STATISTICS_OIS_SAMPLES android.statistics.oisSamples}. Although OIS could be the
+     * main driver, all relevant factors such as focus distance and optical zoom must also
+     * be included. Do note that OIS samples must not be applied on top of the lens intrinsic
+     * samples.
+     * Support for this capture result can be queried via
+     * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.
+     * If available, clients can expect multiple samples per capture result. The specific
+     * amount will depend on current frame duration and sampling rate. Generally a sampling rate
+     * greater than or equal to 200Hz is considered sufficient for high quality results.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
+     * @see CaptureResult#STATISTICS_OIS_SAMPLES
+     */
+    @PublicKey
+    @NonNull
+    @SyntheticKey
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    public static final Key<android.hardware.camera2.params.LensIntrinsicsSample[]> STATISTICS_LENS_INTRINSICS_SAMPLES =
+            new Key<android.hardware.camera2.params.LensIntrinsicsSample[]>("android.statistics.lensIntrinsicsSamples", android.hardware.camera2.params.LensIntrinsicsSample[].class);
+
+    /**
+     * <p>An array of timestamps of lens intrinsics samples, in nanoseconds.</p>
+     * <p>The array contains the timestamps of lens intrinsics samples. The timestamps are in the
+     * same timebase as and comparable to {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp}.</p>
+     * <p><b>Units</b>: nanoseconds</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureResult#SENSOR_TIMESTAMP
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    public static final Key<long[]> STATISTICS_LENS_INTRINSIC_TIMESTAMPS =
+            new Key<long[]>("android.statistics.lensIntrinsicTimestamps", long[].class);
+
+    /**
+     * <p>An array of intra-frame lens intrinsics.</p>
+     * <p>The data layout and contents of individual array entries matches with
+     * {@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}.</p>
+     * <p><b>Units</b>:
+     * Pixels in the {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} coordinate system.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    public static final Key<float[]> STATISTICS_LENS_INTRINSIC_SAMPLES =
+            new Key<float[]>("android.statistics.lensIntrinsicSamples", float[].class);
+
+    /**
      * <p>Tonemapping / contrast / gamma curve for the blue
      * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
      * CONTRAST_CURVE.</p>
@@ -5668,6 +5722,55 @@
             new Key<String>("android.logicalMultiCamera.activePhysicalId", String.class);
 
     /**
+     * <p>The current region of the active physical sensor that will be read out for this
+     * capture.</p>
+     * <p>This capture result matches with {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} on non-logical single
+     * camera sensor devices. In case of logical cameras that can switch between several
+     * physical devices in response to {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, this capture result will
+     * not behave like {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} and {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, where the
+     * combination of both reflects the effective zoom and crop of the logical camera output.
+     * Instead, this capture result value will describe the zoom and crop of the active physical
+     * device. Some examples of when the value of this capture result will change include
+     * switches between different physical lenses, switches between regular and maximum
+     * resolution pixel mode and going through the device digital or optical range.
+     * This capture result is similar to {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} with respect to distortion
+     * correction. When the distortion correction mode is OFF, the coordinate system follows
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with (0, 0) being the top-left pixel
+     * of the pre-correction active array. When the distortion correction mode is not OFF,
+     * the coordinate system follows {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with (0, 0) being
+     * the top-left pixel of the active array.</p>
+     * <p>For camera devices with the
+     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
+     * capability or devices where {@link CameraCharacteristics#getAvailableCaptureRequestKeys }
+     * lists {@link CaptureRequest#SENSOR_PIXEL_MODE {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode}}
+     * , the current active physical device
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.activeArraySizeMaximumResolution} /
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION android.sensor.info.preCorrectionActiveArraySizeMaximumResolution} must be used as the
+     * coordinate system for requests where {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+     * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+     * <p><b>Units</b>: Pixel coordinates relative to
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} of the currently
+     * {@link CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID android.logicalMultiCamera.activePhysicalId} depending on distortion correction capability
+     * and mode</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#CONTROL_ZOOM_RATIO
+     * @see CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID
+     * @see CaptureRequest#SCALER_CROP_REGION
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CaptureRequest#SENSOR_PIXEL_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    public static final Key<android.graphics.Rect> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION =
+            new Key<android.graphics.Rect>("android.logicalMultiCamera.activePhysicalSensorCropRegion", android.graphics.Rect.class);
+
+    /**
      * <p>Mode of operation for the lens distortion correction block.</p>
      * <p>The lens distortion correction block attempts to improve image quality by fixing
      * radial, tangential, or other geometric aberrations in the camera device's optics.  If
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 9743c1f..3affb73 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -55,6 +55,7 @@
 import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.Face;
 import android.hardware.camera2.params.HighSpeedVideoConfiguration;
+import android.hardware.camera2.params.LensIntrinsicsSample;
 import android.hardware.camera2.params.LensShadingMap;
 import android.hardware.camera2.params.MandatoryStreamCombination;
 import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
@@ -849,6 +850,15 @@
                         return (T) metadata.getMultiResolutionStreamConfigurationMap();
                     }
                 });
+        sGetCommandMap.put(
+                CaptureResult.STATISTICS_LENS_INTRINSICS_SAMPLES.getNativeKey(),
+                new GetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> T getValue(CameraMetadataNative metadata, Key<T> key) {
+                        return (T) metadata.getLensIntrinsicSamples();
+                    }
+                });
     }
 
     private int[] getAvailableFormats() {
@@ -1780,6 +1790,56 @@
         return samples;
     }
 
+    private boolean setLensIntrinsicsSamples(LensIntrinsicsSample[] samples) {
+        if (samples == null) {
+            return false;
+        }
+
+        long[] tsArray = new long[samples.length];
+        float[] intrinsicsArray = new float[samples.length * 5];
+        for (int i = 0; i < samples.length; i++) {
+            tsArray[i] = samples[i].getTimestamp();
+            System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5*i, 5);
+
+        }
+        setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray);
+        setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray);
+
+        return true;
+    }
+
+    private LensIntrinsicsSample[] getLensIntrinsicSamples() {
+        long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS);
+        float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES);
+
+        if (timestamps == null) {
+            if (intrinsics != null) {
+                throw new AssertionError("timestamps is null but intrinsics is not");
+            }
+
+            return null;
+        }
+
+        if (intrinsics == null) {
+            throw new AssertionError("timestamps is not null but intrinsics is");
+        } else if((intrinsics.length % 5) != 0) {
+            throw new AssertionError("intrinsics are not multiple of 5");
+        }
+
+        if ((intrinsics.length / 5) != timestamps.length) {
+            throw new AssertionError(String.format(
+                    "timestamps has %d entries but intrinsics has %d", timestamps.length,
+                    intrinsics.length / 5));
+        }
+
+        LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length];
+        for (int i = 0; i < timestamps.length; i++) {
+            float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5*i, 5*i + 5);
+            samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic);
+        }
+        return samples;
+    }
+
     private Capability[] getExtendedSceneModeCapabilities() {
         int[] maxSizes =
                 getBase(CameraCharacteristics.CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_MAX_SIZES);
@@ -1947,6 +2007,15 @@
                         metadata.setLensShadingMap((LensShadingMap) value);
                     }
                 });
+        sSetCommandMap.put(
+                CaptureResult.STATISTICS_LENS_INTRINSICS_SAMPLES.getNativeKey(),
+                new SetCommand() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> void setValue(CameraMetadataNative metadata, T value) {
+                        metadata.setLensIntrinsicsSamples((LensIntrinsicsSample []) value);
+                    }
+                });
     }
 
     private boolean setAvailableFormats(int[] value) {
diff --git a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
new file mode 100644
index 0000000..575cbfa
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.params;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.utils.HashCodeHelpers;
+import android.text.TextUtils;
+
+import com.android.internal.camera.flags.Flags;
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+
+/**
+ * Immutable class to store an
+ * {@link CaptureResult#STATISTICS_LENS_INTRINSICS_SAMPLES lens intrinsics intra-frame sample}.
+ */
+@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+public final class LensIntrinsicsSample {
+    /**
+     * Create a new {@link LensIntrinsicsSample}.
+     *
+     * <p>{@link LensIntrinsicsSample} contains the timestamp and the
+     * {@link CaptureResult#LENS_INTRINSIC_CALIBRATION} sample.
+     *
+     * @param timestamp timestamp of the lens intrinsics sample.
+     * @param lensIntrinsics the lens intrinsic calibration for the sample.
+     *
+     * @throws IllegalArgumentException if lensIntrinsics length is different from 5
+     */
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    public LensIntrinsicsSample(final long timestamp, @NonNull final float[] lensIntrinsics) {
+        mTimestampNs = timestamp;
+        Preconditions.checkArgument(lensIntrinsics.length == 5);
+        mLensIntrinsics = lensIntrinsics;
+    }
+
+    /**
+     * Get the timestamp in nanoseconds.
+     *
+     *<p>The timestamps are in the same timebase as and comparable to
+     *{@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp}.</p>
+     *
+     * @return a long value (guaranteed to be finite)
+     */
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    public long getTimestamp() {
+        return mTimestampNs;
+    }
+
+    /**
+     * Get the lens intrinsics calibration
+     *
+     * @return a floating point value (guaranteed to be finite)
+     * @see CaptureResult#LENS_INTRINSIC_CALIBRATION
+     */
+    @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+    @NonNull
+    public float[] getLensIntrinsics() {
+        return mLensIntrinsics;
+    }
+
+    /**
+     * Check if this {@link LensIntrinsicsSample} is equal to another {@link LensIntrinsicsSample}.
+     *
+     * <p>Two samples are only equal if and only if each of the lens intrinsics are equal.</p>
+     *
+     * @return {@code true} if the objects were equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        } else if (this == obj) {
+            return true;
+        } else if (obj instanceof LensIntrinsicsSample) {
+            final LensIntrinsicsSample other = (LensIntrinsicsSample) obj;
+            return mTimestampNs == other.mTimestampNs
+                    && Arrays.equals(mLensIntrinsics, other.getLensIntrinsics());
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        int timestampHash = HashCodeHelpers.hashCode(((float)mTimestampNs));
+        return HashCodeHelpers.hashCode(Arrays.hashCode(mLensIntrinsics), timestampHash);
+    }
+
+    /**
+     * Return the LensIntrinsicsSample as a string representation.
+     *
+     * <p> {@code "LensIntrinsicsSample{timestamp:%l, sample:%s}"} represents the LensIntrinsics
+     * sample's timestamp, and calibration data.</p>
+     *
+     * @return string representation of {@link LensIntrinsicsSample}
+     */
+    @Override
+    public String toString() {
+        return TextUtils.formatSimple("LensIntrinsicsSample{timestamp:%d, sample:%s}", mTimestampNs,
+               Arrays.toString(mLensIntrinsics));
+    }
+
+    private final long mTimestampNs;
+    private final float [] mLensIntrinsics;
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 88d7231..6626baf 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -169,6 +169,8 @@
 
     void setPointerIconType(int typeId);
     void setCustomPointerIcon(in PointerIcon icon);
+    boolean setPointerIcon(in PointerIcon icon, int displayId, int deviceId, int pointerId,
+            in IBinder inputToken);
 
     oneway void requestPointerCapture(IBinder inputChannelToken, boolean enabled);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index abbf954..f941ad8 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1057,6 +1057,12 @@
         mGlobal.setCustomPointerIcon(icon);
     }
 
+    /** @hide */
+    public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
+            IBinder inputToken) {
+        return mGlobal.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
+    }
+
     /**
      * Check if showing a {@link android.view.PointerIcon} for styluses is enabled.
      *
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index cf1dfe3..24a6911 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1286,6 +1286,18 @@
     }
 
     /**
+     * @see InputManager#setPointerIcon(PointerIcon, int, int, int, IBinder)
+     */
+    public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
+            IBinder inputToken) {
+        try {
+            return mIm.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * @see InputManager#requestPointerCapture(IBinder, boolean)
      */
     public void requestPointerCapture(IBinder windowToken, boolean enable) {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 86628d9..f2930fe 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -27,6 +27,10 @@
 import android.annotation.TestApi;
 import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
+import android.ravenwood.annotation.RavenwoodThrow;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -228,6 +232,8 @@
  * {@link #readMap(Map, ClassLoader, Class, Class)},
  * {@link #readSparseArray(ClassLoader, Class)}.
  */
+@RavenwoodKeepWholeClass
+@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.Parcel_host")
 public final class Parcel {
 
     private static final boolean DEBUG_RECYCLE = false;
@@ -382,8 +388,10 @@
     @CriticalNative
     private static native void nativeMarkSensitive(long nativePtr);
     @FastNative
+    @RavenwoodThrow
     private static native void nativeMarkForBinder(long nativePtr, IBinder binder);
     @CriticalNative
+    @RavenwoodThrow
     private static native boolean nativeIsForRpc(long nativePtr);
     @CriticalNative
     private static native int nativeDataSize(long nativePtr);
@@ -415,14 +423,17 @@
     private static native int nativeWriteFloat(long nativePtr, float val);
     @CriticalNative
     private static native int nativeWriteDouble(long nativePtr, double val);
+    @RavenwoodThrow
     private static native void nativeSignalExceptionForError(int error);
     @FastNative
     private static native void nativeWriteString8(long nativePtr, String val);
     @FastNative
     private static native void nativeWriteString16(long nativePtr, String val);
     @FastNative
+    @RavenwoodThrow
     private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
     @FastNative
+    @RavenwoodThrow
     private static native void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
 
     private static native byte[] nativeCreateByteArray(long nativePtr);
@@ -441,8 +452,10 @@
     @FastNative
     private static native String nativeReadString16(long nativePtr);
     @FastNative
+    @RavenwoodThrow
     private static native IBinder nativeReadStrongBinder(long nativePtr);
     @FastNative
+    @RavenwoodThrow
     private static native FileDescriptor nativeReadFileDescriptor(long nativePtr);
 
     private static native long nativeCreate();
@@ -452,7 +465,9 @@
     private static native byte[] nativeMarshall(long nativePtr);
     private static native void nativeUnmarshall(
             long nativePtr, byte[] data, int offset, int length);
+    @RavenwoodThrow
     private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
+    @RavenwoodThrow
     private static native boolean nativeCompareDataInRange(
             long ptrA, int offsetA, long ptrB, int offsetB, int length);
     private static native void nativeAppendFrom(
@@ -461,13 +476,17 @@
     private static native boolean nativeHasFileDescriptors(long nativePtr);
     private static native boolean nativeHasFileDescriptorsInRange(
             long nativePtr, int offset, int length);
+    @RavenwoodThrow
     private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
+    @RavenwoodThrow
     private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
 
     @CriticalNative
+    @RavenwoodThrow
     private static native boolean nativeReplaceCallingWorkSourceUid(
             long nativePtr, int workSourceUid);
     @CriticalNative
+    @RavenwoodThrow
     private static native int nativeReadCallingWorkSourceUid(long nativePtr);
 
     /** Last time exception with a stack trace was written */
@@ -476,6 +495,7 @@
     private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000;
 
     @CriticalNative
+    @RavenwoodThrow
     private static native long nativeGetOpenAshmemSize(long nativePtr);
 
     public final static Parcelable.Creator<String> STRING_CREATOR
@@ -634,10 +654,12 @@
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RavenwoodThrow
     public static native long getGlobalAllocSize();
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @RavenwoodThrow
     public static native long getGlobalAllocCount();
 
     /**
@@ -2918,6 +2940,7 @@
      * @see #writeNoException
      * @see #readException
      */
+    @RavenwoodReplace
     public final void writeException(@NonNull Exception e) {
         AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
 
@@ -3017,6 +3040,7 @@
      * @see #writeException
      * @see #readException
      */
+    @RavenwoodReplace
     public final void writeNoException() {
         AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
 
diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java
index f2b60a4..b5c09bb 100644
--- a/core/java/android/os/Parcelable.java
+++ b/core/java/android/os/Parcelable.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -86,6 +87,7 @@
  *     }
  * }</pre></section></div></div>
  */
+@RavenwoodKeepWholeClass
 public interface Parcelable {
     /** @hide */
     @IntDef(flag = true, prefix = { "PARCELABLE_" }, value = {
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 0d0d1da..5d7e04d 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -444,7 +444,8 @@
      * these characters they will be replaced with a space character in the trace.
      *
      * @param sectionName The name of the code section to appear in the trace.  This may be at
-     * most 127 Unicode code units long.
+     *                    most 127 Unicode code units long.
+     * @throws IllegalArgumentException if {@code sectionName} is too long.
      */
     public static void beginSection(@NonNull String sectionName) {
         if (isTagEnabled(TRACE_TAG_APP)) {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 69d86a6..437668c 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -44,3 +44,10 @@
     description: "Enables the independent keyboard vibration settings feature"
     bug: "289107579"
 }
+
+flag {
+    namespace: "haptics"
+    name: "adaptive_haptics_enabled"
+    description: "Enables the adaptive haptics feature"
+    bug: "305961689"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1a33b768..8b5995a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -37,7 +37,6 @@
 import android.app.AppOpsManager;
 import android.app.Application;
 import android.app.AutomaticZenRule;
-import android.app.Flags;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.SearchManager;
@@ -1921,7 +1920,7 @@
      * <p>
      * Output: Nothing.
      */
-    @FlaggedApi(Flags.FLAG_MODES_API)
+    @FlaggedApi(android.app.Flags.FLAG_MODES_API)
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
             = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
@@ -1931,7 +1930,7 @@
      * <p>
      * This must be passed as an extra field to the {@link #ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
      */
-    @FlaggedApi(Flags.FLAG_MODES_API)
+    @FlaggedApi(android.app.Flags.FLAG_MODES_API)
     public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID
             = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
 
@@ -4086,6 +4085,7 @@
          */
         @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
         @SystemApi
+        @FlaggedApi(Flags.FLAG_SYSTEM_SETTINGS_DEFAULT)
         public static boolean putString(@NonNull ContentResolver resolver, @NonNull String name,
                 @Nullable String value, boolean makeDefault, boolean overrideableByRestore) {
             return putStringForUser(resolver, name, value, /* tag= */ null,
@@ -4140,6 +4140,7 @@
          * @hide
          */
         @SystemApi
+        @FlaggedApi(Flags.FLAG_SYSTEM_SETTINGS_DEFAULT)
         public static void resetToDefaults(@NonNull ContentResolver resolver,
                 @Nullable String tag) {
             resetToDefaultsAsUser(resolver, tag, RESET_MODE_PACKAGE_DEFAULTS,
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
new file mode 100644
index 0000000..3dd7692
--- /dev/null
+++ b/core/java/android/provider/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.provider"
+
+flag {
+    name: "system_settings_default"
+    namespace: "package_manager_service"
+    description: "Enable Settings.System.resetToDefault APIs."
+    bug: "279083734"
+}
diff --git a/core/java/android/service/chooser/CustomChoosers.java b/core/java/android/service/chooser/CustomChoosers.java
new file mode 100644
index 0000000..5b89432
--- /dev/null
+++ b/core/java/android/service/chooser/CustomChoosers.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.chooser;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Static helper methods that privileged clients can use to initiate Share sessions with extra
+ * customization options that aren't usually available in the stock "Resolver/Chooser" flows.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_SUPPORT_NFC_RESOLVER)
+@SystemApi
+public class CustomChoosers {
+    /**
+     * Intent action to start a Share session with additional customization options. Clients should
+     * use the helper methods in this class to configure their customized share intents, and should
+     * avoid using this action to construct their own intents directly.
+     */
+    private static final String ACTION_SHOW_CUSTOMIZED_RESOLVER =
+            "android.service.chooser.action.SHOW_CUSTOMIZED_RESOLVER";
+
+    /**
+     * "Extras" key for an ArrayList of {@link ResolveInfo} records which are to be shown as the
+     * targets in the customized share session.
+     *
+     * @hide
+     */
+    public static final String EXTRA_RESOLVE_INFOS = "android.service.chooser.extra.RESOLVE_INFOS";
+
+    /**
+     * Build an {@link Intent} to dispatch a "Chooser flow" that picks a target resolution for the
+     * specified {@code target} intent, styling the Chooser UI according to the specified
+     * customization parameters.
+     *
+     * @param target The ambiguous intent that should be resolved to a specific target selected
+     * via the Chooser flow.
+     * @param title An optional "headline" string to display at the top of the Chooser UI, or null
+     * to use the system default.
+     * @param resolutionList Explicit resolution info for targets that should be shown in the
+     * dispatched Share UI.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_NFC_RESOLVER)
+    @SystemApi
+    @NonNull
+    public static Intent createNfcResolverIntent(
+            @NonNull Intent target,
+            @Nullable CharSequence title,
+            @NonNull List<ResolveInfo> resolutionList) {
+        Intent resolverIntent = new Intent(ACTION_SHOW_CUSTOMIZED_RESOLVER);
+        resolverIntent.putExtra(Intent.EXTRA_INTENT, target);
+        resolverIntent.putExtra(Intent.EXTRA_TITLE, title);
+        resolverIntent.putParcelableArrayListExtra(
+                EXTRA_RESOLVE_INFOS, new ArrayList<>(resolutionList));
+        return resolverIntent;
+    }
+
+    private CustomChoosers() {}
+}
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index d76fa5b..531626b 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -257,7 +257,10 @@
         throw new IllegalArgumentException("state is invalid: " + state);
     }
 
-    /** Provides a human-readable string version of the Source enum. */
+    /**
+     * Provides a human-readable string version of the Source enum.
+     * @hide
+     */
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static @NonNull String sourceToString(@Source int source) {
         if (source == SOURCE_UNKNOWN) return "SOURCE_UNKNOWN";
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 3a4a0c5..b1680ab 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -1241,7 +1241,7 @@
      * @hide
      */
     public byte[] toProto() {
-        // TODO: b/308672510 - log new ZenPolicy fields to DNDPolicyProto.
+        // TODO: b/308672510 - log user-customized ZenPolicy fields to DNDPolicyProto.
         ByteArrayOutputStream bytes = new ByteArrayOutputStream();
         ProtoOutputStream proto = new ProtoOutputStream(bytes);
 
@@ -1267,6 +1267,10 @@
         proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM, getPriorityMessageSenders());
         proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders());
 
+        if (Flags.modesApi()) {
+            proto.write(DNDPolicyProto.ALLOW_CHANNELS, getAllowedChannels());
+        }
+
         proto.flush();
         return bytes.toByteArray();
     }
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 7f313c1..cd80a0b 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -22,6 +22,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
+import android.annotation.TestApi;
 import android.app.AppOpsManager;
 import android.app.Service;
 import android.content.AttributionSource;
@@ -514,9 +515,15 @@
     @Override
     public final IBinder onBind(final Intent intent) {
         if (DBG) Log.d(TAG, "#onBind, intent=" + intent);
+        onBindInternal();
         return mBinder;
     }
 
+    /** @hide */
+    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+    @TestApi
+    public void onBindInternal() { }
+
     @Override
     public void onDestroy() {
         if (DBG) Log.d(TAG, "#onDestroy");
diff --git a/core/java/android/speech/SpeechRecognizerImpl.java b/core/java/android/speech/SpeechRecognizerImpl.java
index 6c00874..f3f17de 100644
--- a/core/java/android/speech/SpeechRecognizerImpl.java
+++ b/core/java/android/speech/SpeechRecognizerImpl.java
@@ -61,6 +61,7 @@
     private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5;
     private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6;
     private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7;
+    private static final int MSG_DESTROY = 8;
 
     /** The actual RecognitionService endpoint */
     private IRecognitionService mService;
@@ -77,39 +78,31 @@
     private IRecognitionServiceManager mManagerService;
 
     /** Handler that will execute the main tasks */
-    private Handler mHandler = new Handler(Looper.getMainLooper()) {
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_START:
-                    handleStartListening((Intent) msg.obj);
-                    break;
-                case MSG_STOP:
-                    handleStopMessage();
-                    break;
-                case MSG_CANCEL:
-                    handleCancelMessage();
-                    break;
-                case MSG_CHANGE_LISTENER:
-                    handleChangeListener((RecognitionListener) msg.obj);
-                    break;
-                case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT:
-                    handleSetTemporaryComponent((ComponentName) msg.obj);
-                    break;
-                case MSG_CHECK_RECOGNITION_SUPPORT:
+                case MSG_START -> handleStartListening((Intent) msg.obj);
+                case MSG_STOP -> handleStopMessage();
+                case MSG_CANCEL -> handleCancelMessage();
+                case MSG_CHANGE_LISTENER -> handleChangeListener((RecognitionListener) msg.obj);
+                case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT ->
+                        handleSetTemporaryComponent((ComponentName) msg.obj);
+                case MSG_CHECK_RECOGNITION_SUPPORT -> {
                     CheckRecognitionSupportArgs args = (CheckRecognitionSupportArgs) msg.obj;
                     handleCheckRecognitionSupport(
                             args.mIntent, args.mCallbackExecutor, args.mCallback);
-                    break;
-                case MSG_TRIGGER_MODEL_DOWNLOAD:
+                }
+                case MSG_TRIGGER_MODEL_DOWNLOAD -> {
                     ModelDownloadListenerArgs modelDownloadListenerArgs =
                             (ModelDownloadListenerArgs) msg.obj;
                     handleTriggerModelDownload(
                             modelDownloadListenerArgs.mIntent,
                             modelDownloadListenerArgs.mExecutor,
                             modelDownloadListenerArgs.mModelDownloadListener);
-                    break;
+                }
+                case MSG_DESTROY -> handleDestroy();
             }
         }
     };
@@ -433,6 +426,10 @@
 
     @Override
     public void destroy() {
+        putMessage(mHandler.obtainMessage(MSG_DESTROY));
+    }
+
+    private void handleDestroy() {
         if (mService != null) {
             try {
                 mService.cancel(mListener, /*isShutdown*/ true);
diff --git a/core/java/android/tracing/OWNERS b/core/java/android/tracing/OWNERS
index 079d4c5..2ebe2e9 100644
--- a/core/java/android/tracing/OWNERS
+++ b/core/java/android/tracing/OWNERS
@@ -1,3 +1,6 @@
 carmenjackson@google.com
 kevinjeon@google.com
+pablogamito@google.com
+natanieljr@google.com
+keanmariotti@google.com
 include platform/external/perfetto:/OWNERS
diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index 4b4f6d6..c6e8844 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -5,4 +5,11 @@
     namespace: "windowing_tools"
     description: "Move transition tracing to Perfetto"
     bug: "309630341"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "perfetto_protolog"
+    namespace: "windowing_tools"
+    description: "Migrate protolog to Perfetto"
+    bug: "276432490"
+}
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 9148c4a..f14485b 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -16,6 +16,9 @@
 
 package android.util;
 
+import static com.android.window.flags.Flags.FLAG_DENSITY_390_API;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -59,6 +62,7 @@
             DENSITY_XHIGH,
             DENSITY_340,
             DENSITY_360,
+            DENSITY_390,
             DENSITY_400,
             DENSITY_420,
             DENSITY_440,
@@ -182,6 +186,15 @@
      * This is not a density that applications should target, instead relying
      * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
      */
+    @FlaggedApi(FLAG_DENSITY_390_API)
+    public static final int DENSITY_390 = 390;
+
+    /**
+     * Intermediate density for screens that sit somewhere between
+     * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi).
+     * This is not a density that applications should target, instead relying
+     * on the system to scale their {@link #DENSITY_XXHIGH} assets for them.
+     */
     public static final int DENSITY_400 = 400;
 
     /**
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index d131dc9..f2c3abc 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1308,24 +1308,6 @@
     }
 
     /**
-     * Sets the current pointer type.
-     * @param pointerType the type of the pointer icon.
-     * @hide
-     */
-    public void setPointerType(int pointerType) {
-        InputManagerGlobal.getInstance().setPointerIconType(pointerType);
-    }
-
-    /**
-     * Specifies the current custom pointer.
-     * @param icon the icon data.
-     * @hide
-     */
-    public void setCustomPointerIcon(PointerIcon icon) {
-        InputManagerGlobal.getInstance().setCustomPointerIcon(icon);
-    }
-
-    /**
      * Reports whether the device has a battery.
      * @return true if the device has a battery, false otherwise.
      * @hide
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index fee88d91..7800c28 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -223,6 +223,9 @@
      * @throws IllegalArgumentException if context is null.
      */
     public static @NonNull PointerIcon getSystemIcon(@NonNull Context context, int type) {
+        // TODO(b/293587049): Pointer Icon Refactor: There is no need to load the system
+        // icon resource into memory outside of system server. Remove the need to load
+        // resources when getting a system icon.
         if (context == null) {
             throw new IllegalArgumentException("context must not be null");
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2fcd675..8d469ee 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -97,6 +97,8 @@
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
 
+import static com.android.input.flags.Flags.enablePointerChoreographer;
+
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
 import android.animation.AnimationHandler;
@@ -123,6 +125,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.database.ContentObserver;
 import android.graphics.BLASTBufferQueue;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -392,6 +395,9 @@
      */
     private CompatOnBackInvokedCallback mCompatOnBackInvokedCallback;
 
+    @Nullable
+    private ContentObserver mForceInvertObserver;
+
     /**
      * Callback for notifying about global configuration changes.
      */
@@ -1597,6 +1603,24 @@
                         | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
                         | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
                         mBasePackageName);
+
+        if (forceInvertColor()) {
+            if (mForceInvertObserver == null) {
+                mForceInvertObserver = new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        updateForceDarkMode();
+                    }
+                };
+                mContext.getContentResolver().registerContentObserver(
+                        Settings.Secure.getUriFor(
+                                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED
+                        ),
+                        false,
+                        mForceInvertObserver,
+                        UserHandle.myUserId());
+            }
+        }
     }
 
     /**
@@ -1610,6 +1634,14 @@
         DisplayManagerGlobal
                 .getInstance()
                 .unregisterDisplayListener(mDisplayListener);
+
+        if (forceInvertColor()) {
+            if (mForceInvertObserver != null) {
+                mContext.getContentResolver().unregisterContentObserver(mForceInvertObserver);
+                mForceInvertObserver = null;
+            }
+        }
+
         if (mExtraDisplayListenerLogging) {
             Slog.w(mTag, "Unregister listeners: " + mBasePackageName, new Throwable());
         }
@@ -7455,18 +7487,34 @@
             mPointerIconType = pointerType;
             mCustomPointerIcon = null;
             if (mPointerIconType != PointerIcon.TYPE_CUSTOM) {
-                InputManagerGlobal
-                    .getInstance()
-                    .setPointerIconType(pointerType);
+                if (enablePointerChoreographer()) {
+                    InputManagerGlobal
+                            .getInstance()
+                            .setPointerIcon(PointerIcon.getSystemIcon(mContext, pointerType),
+                                    event.getDisplayId(), event.getDeviceId(),
+                                    event.getPointerId(pointerIndex), getInputToken());
+                } else {
+                    InputManagerGlobal
+                            .getInstance()
+                            .setPointerIconType(pointerType);
+                }
                 return true;
             }
         }
         if (mPointerIconType == PointerIcon.TYPE_CUSTOM &&
                 !pointerIcon.equals(mCustomPointerIcon)) {
             mCustomPointerIcon = pointerIcon;
-            InputManagerGlobal
-                    .getInstance()
-                    .setCustomPointerIcon(mCustomPointerIcon);
+            if (enablePointerChoreographer()) {
+                InputManagerGlobal
+                        .getInstance()
+                        .setPointerIcon(mCustomPointerIcon,
+                                event.getDisplayId(), event.getDeviceId(),
+                                event.getPointerId(pointerIndex), getInputToken());
+            } else {
+                InputManagerGlobal
+                        .getInstance()
+                        .setCustomPointerIcon(mCustomPointerIcon);
+            }
         }
         return true;
     }
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 07ae134..3dbe65e 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1823,13 +1823,13 @@
 
     /**
      *
-     * Sets an {@link IWindowMagnificationConnection} that manipulates window magnification.
+     * Sets an {@link IMagnificationConnection} that manipulates magnification in SystemUI.
      *
-     * @param connection The connection that manipulates window magnification.
+     * @param connection The connection that manipulates magnification in SystemUI.
      * @hide
      */
-    public void setWindowMagnificationConnection(@Nullable
-            IWindowMagnificationConnection connection) {
+    public void setMagnificationConnection(@Nullable
+            IMagnificationConnection connection) {
         final IAccessibilityManager service;
         synchronized (mLock) {
             service = getServiceLocked();
@@ -1838,9 +1838,9 @@
             }
         }
         try {
-            service.setWindowMagnificationConnection(connection);
+            service.setMagnificationConnection(connection);
         } catch (RemoteException re) {
-            Log.e(LOG_TAG, "Error setting window magnfication connection", re);
+            Log.e(LOG_TAG, "Error setting magnification connection", re);
         }
     }
 
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 7a1112f..f741080 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -27,7 +27,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnection;
 import android.view.accessibility.IAccessibilityManagerClient;
 import android.view.accessibility.AccessibilityWindowAttributes;
-import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.InputEvent;
 import android.view.IWindow;
 import android.view.MagnificationSpec;
@@ -90,7 +90,7 @@
 
     oneway void registerSystemAction(in RemoteAction action, int actionId);
     oneway void unregisterSystemAction(int actionId);
-    oneway void setWindowMagnificationConnection(in IWindowMagnificationConnection connection);
+    oneway void setMagnificationConnection(in IMagnificationConnection connection);
 
     void associateEmbeddedHierarchy(IBinder host, IBinder embedded);
 
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IMagnificationConnection.aidl
similarity index 98%
rename from core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
rename to core/java/android/view/accessibility/IMagnificationConnection.aidl
index a404bd6..a5e8aaf 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IMagnificationConnection.aidl
@@ -23,11 +23,11 @@
 
 /**
  * Interface for interaction between {@link AccessibilityManagerService}
- * and {@link WindowMagnification} in SystemUI.
+ * and {@link Magnification} in SystemUI.
  *
  * @hide
  */
-oneway interface IWindowMagnificationConnection {
+oneway interface IMagnificationConnection {
 
     /**
      * Enables window magnification on specified display with given center and scale and animation.
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 6884e63..6963237 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -91,7 +91,6 @@
 
     private final WeakReference<Context> mContext;
     private final Resources mResources;
-    private final WeakReference<WindowManager> mWindowManager;
     private final IAccessibilityManager mAccessibilityManagerService;
     private final INotificationManager mNotificationManager;
     private final String mPackageName;
@@ -104,7 +103,6 @@
             INotificationManager notificationManager, String packageName) {
         mContext = new WeakReference<>(context);
         mResources = context.getResources();
-        mWindowManager = new WeakReference<>(context.getSystemService(WindowManager.class));
         mNotificationManager = notificationManager;
         mPackageName = packageName;
         mContextPackageName = context.getPackageName();
@@ -274,7 +272,7 @@
     public void hide(@Nullable ITransientNotificationCallback callback) {
         checkState(mView != null, "No toast to hide.");
 
-        final WindowManager windowManager = mWindowManager.get();
+        final WindowManager windowManager = getWindowManager(mView);
         if (mView.getParent() != null && windowManager != null) {
             windowManager.removeViewImmediate(mView);
         }
@@ -295,6 +293,17 @@
         mToken = null;
     }
 
+    private WindowManager getWindowManager(View view) {
+        Context context = mContext.get();
+        if (context == null && view != null) {
+            context = view.getContext();
+        }
+        if (context != null) {
+            return context.getSystemService(WindowManager.class);
+        }
+        return null;
+    }
+
     /**
      * Sends {@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED} event if accessibility is
      * enabled.
@@ -331,7 +340,7 @@
     }
 
     private void addToastView() {
-        final WindowManager windowManager = mWindowManager.get();
+        final WindowManager windowManager = getWindowManager(mView);
         if (windowManager == null) {
             return;
         }
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 492e2ac..3a321e5 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -140,14 +140,26 @@
     void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName, long version);
 
     SyncNotedAppOp noteProxyOperationWithState(int code,
-                in AttributionSourceState attributionSourceStateState,
-                boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-                boolean skipProxyOperation);
+            in AttributionSourceState attributionSourceStateState,
+            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+            boolean skipProxyOperation);
     SyncNotedAppOp startProxyOperationWithState(IBinder clientId, int code,
-                in AttributionSourceState attributionSourceStateState, boolean startIfModeDefault,
-                boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
-                boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags,
-                int attributionChainId);
+            in AttributionSourceState attributionSourceStateState, boolean startIfModeDefault,
+            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+            boolean skipProxyOperation, int proxyAttributionFlags, int proxiedAttributionFlags,
+            int attributionChainId);
     void finishProxyOperationWithState(IBinder clientId, int code,
-                in AttributionSourceState attributionSourceStateState, boolean skipProxyOperation);
+            in AttributionSourceState attributionSourceStateState, boolean skipProxyOperation);
+    int checkOperationRawForDevice(int code, int uid, String packageName,
+            @nullable String attributionTag, int virtualDeviceId);
+    int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId);
+    SyncNotedAppOp noteOperationForDevice(int code, int uid, String packageName,
+            @nullable String attributionTag, int virtualDeviceId,
+            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage);
+    SyncNotedAppOp startOperationForDevice(IBinder clientId, int code, int uid, String packageName,
+            @nullable String attributionTag,  int virtualDeviceId, boolean startIfModeDefault,
+            boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
+            int attributionFlags, int attributionChainId);
+    void finishOperationForDevice(IBinder clientId, int code, int uid, String packageName,
+            @nullable String attributionTag, int virtualDeviceId);
 }
diff --git a/core/java/com/android/internal/app/NfcResolverActivity.java b/core/java/com/android/internal/app/NfcResolverActivity.java
new file mode 100644
index 0000000..402192a
--- /dev/null
+++ b/core/java/com/android/internal/app/NfcResolverActivity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import static android.service.chooser.CustomChoosers.EXTRA_RESOLVE_INFOS;
+import static android.service.chooser.Flags.supportNfcResolver;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+/**
+ * Caller-customizable variant of {@link ResolverActivity} to support the
+ * {@link CustomChoosers#showNfcResolver()} API.
+ */
+public class NfcResolverActivity extends ResolverActivity {
+
+    @Override
+    @SuppressWarnings("MissingSuperCall")  // Called indirectly via `super_onCreate()`.
+    protected void onCreate(Bundle savedInstanceState) {
+        if (!supportNfcResolver()) {
+            super_onCreate(savedInstanceState);
+            finish();
+            return;
+        }
+
+        Intent intent = getIntent();
+        Intent target = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
+        ArrayList<ResolveInfo> rList =
+            intent.getParcelableArrayListExtra(EXTRA_RESOLVE_INFOS, ResolveInfo.class);
+        CharSequence title = intent.getExtras().getCharSequence(
+                Intent.EXTRA_TITLE,
+                getResources().getText(com.android.internal.R.string.chooseActivity));
+
+        super.onCreate(
+                savedInstanceState,
+                target,
+                title,
+                /* initialIntents=*/ null,
+                rList,
+                /* supportsAlwaysUseOption=*/ false);
+    }
+}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 4d8eeac..5351c6d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -285,12 +285,12 @@
     void suppressAmbientDisplay(boolean suppress);
 
     /**
-     * Requests {@link WindowMagnification} to set window magnification connection through
-     * {@link AccessibilityManager#setWindowMagnificationConnection(IWindowMagnificationConnection)}
+     * Requests {@link Magnification} to set magnification connection to SystemUI through
+     * {@link AccessibilityManager#setMagnificationConnection(IMagnificationConnection)}
      *
      * @param connect {@code true} if needs connection, otherwise set the connection to null.
      */
-    void requestWindowMagnificationConnection(boolean connect);
+    void requestMagnificationConnection(boolean connect);
 
     /**
      * Allow for pass-through arguments from `adb shell cmd statusbar <args>`, and write to the
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index 17ca7c8..a2978be 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -359,6 +359,8 @@
     optional PeopleType allow_messages_from = 18;
 
     optional ConversationType allow_conversations_from = 19;
+
+    optional ChannelType allow_channels = 20;
 }
 
 // Enum identifying the type of rule that changed; values set to match ones used in the
@@ -368,3 +370,11 @@
     RULE_TYPE_MANUAL = 1;
     RULE_TYPE_AUTOMATIC = 2;
 }
+
+// Enum used in DNDPolicyProto to indicate the type of channels permitted to
+// break through DND. Mirrors values in ZenPolicy.
+enum ChannelType {
+    CHANNEL_TYPE_UNSET = 0;
+    CHANNEL_TYPE_PRIORITY = 1;
+    CHANNEL_TYPE_NONE = 2;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cf41a06..c55b808 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2366,7 +2366,7 @@
          @hide
     -->
     <permission android:name="android.permission.SUSPEND_APPS"
-        android:protectionLevel="signature|role" />
+        android:protectionLevel="signature|role|verifier" />
 
     <!-- @SystemApi
          @hide
@@ -7469,6 +7469,13 @@
     <permission android:name="android.permission.SIGNAL_REBOOT_READINESS"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows the holder to launch an Intent Resolver flow with custom presentation
+         and/or targets.
+         @FlaggedApi("android.service.chooser.support_nfc_resolver")
+         @hide -->
+    <permission android:name="android.permission.SHOW_CUSTOMIZED_RESOLVER"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @hide Allows an application to get a People Tile preview for a given shortcut. -->
     <permission android:name="android.permission.GET_PEOPLE_TILE_PREVIEW"
         android:protectionLevel="signature|recents" />
@@ -7891,6 +7898,18 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+        <activity android:name="com.android.internal.app.NfcResolverActivity"
+                android:theme="@style/Theme.Dialog.Alert"
+                android:finishOnCloseSystemDialogs="true"
+                android:excludeFromRecents="true"
+                android:multiprocess="true"
+                android:permission="android.permission.SHOW_CUSTOMIZED_RESOLVER"
+                android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.chooser.action.SHOW_CUSTOMIZED_RESOLVER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <activity android:name="com.android.internal.app.IntentForwarderActivity"
                 android:finishOnCloseSystemDialogs="true"
                 android:theme="@style/Theme.DeviceDefault.Resolver"
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index 84252f9..e2f2554 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -208,14 +208,14 @@
     }
 
     @Test
-    public void testSetWindowMagnificationConnection() throws Exception {
+    public void testSetMagnificationConnection() throws Exception {
         AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
-        IWindowMagnificationConnection connection = Mockito.mock(
-                IWindowMagnificationConnection.class);
+        IMagnificationConnection connection = Mockito.mock(
+                IMagnificationConnection.class);
 
-        manager.setWindowMagnificationConnection(connection);
+        manager.setMagnificationConnection(connection);
 
-        verify(mMockService).setWindowMagnificationConnection(connection);
+        verify(mMockService).setMagnificationConnection(connection);
     }
 
     @Test
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 1f08955..3cf28c9 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -383,6 +383,8 @@
         <!-- Permission required for ShortcutManagerUsageTest CTS test. -->
         <permission name="android.permission.ACCESS_SHORTCUTS"/>
         <permission name="android.permission.REBOOT"/>
+        <!-- Permission required for NfcResolverActivity CTS tests. -->
+        <permission name="android.permission.SHOW_CUSTOMIZED_RESOLVER"/>
         <!-- Permission required for access VIBRATOR_STATE. -->
         <permission name="android.permission.ACCESS_VIBRATOR_STATE"/>
         <!-- Permission required for UsageStatsTest CTS test. -->
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index b2da233..6395179 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -681,12 +681,6 @@
         }
     };
 
-    /** @removed
-     * @deprecated Subsumed by {@link #DecodeException}.
-     */
-    @Deprecated
-    public static class IncompleteException extends IOException {};
-
     /**
      *  Interface for changing the default settings of a decode.
      *
@@ -713,24 +707,6 @@
 
     };
 
-    /** @removed
-     * @deprecated Replaced by {@link #DecodeException#SOURCE_EXCEPTION}.
-     */
-    @Deprecated
-    public static final int ERROR_SOURCE_EXCEPTION  = 1;
-
-    /** @removed
-     * @deprecated Replaced by {@link #DecodeException#SOURCE_INCOMPLETE}.
-     */
-    @Deprecated
-    public static final int ERROR_SOURCE_INCOMPLETE = 2;
-
-    /** @removed
-     * @deprecated Replaced by {@link #DecodeException#SOURCE_MALFORMED_DATA}.
-     */
-    @Deprecated
-    public static final int ERROR_SOURCE_ERROR      = 3;
-
     /**
      *  Information about an interrupted decode.
      */
@@ -1178,14 +1154,6 @@
     }
 
     // Modifiers
-    /** @removed
-     * @deprecated Renamed to {@link #setTargetSize}.
-     */
-    @Deprecated
-    public ImageDecoder setResize(int width, int height) {
-        this.setTargetSize(width, height);
-        return this;
-    }
 
     /**
      *  Specify the size of the output {@link Drawable} or {@link Bitmap}.
@@ -1217,15 +1185,6 @@
         mDesiredHeight = height;
     }
 
-    /** @removed
-     * @deprecated Renamed to {@link #setTargetSampleSize}.
-     */
-    @Deprecated
-    public ImageDecoder setResize(int sampleSize) {
-        this.setTargetSampleSize(sampleSize);
-        return this;
-    }
-
     private int getTargetDimension(int original, int sampleSize, int computed) {
         // Sampling will never result in a smaller size than 1.
         if (sampleSize >= original) {
@@ -1381,15 +1340,6 @@
         mUnpremultipliedRequired = unpremultipliedRequired;
     }
 
-    /** @removed
-     * @deprecated Renamed to {@link #setUnpremultipliedRequired}.
-     */
-    @Deprecated
-    public ImageDecoder setRequireUnpremultiplied(boolean unpremultipliedRequired) {
-        this.setUnpremultipliedRequired(unpremultipliedRequired);
-        return this;
-    }
-
     /**
      *  Return whether the {@link Bitmap} will have unpremultiplied pixels.
      */
@@ -1397,14 +1347,6 @@
         return mUnpremultipliedRequired;
     }
 
-    /** @removed
-     * @deprecated Renamed to {@link #isUnpremultipliedRequired}.
-     */
-    @Deprecated
-    public boolean getRequireUnpremultiplied() {
-        return this.isUnpremultipliedRequired();
-    }
-
     /**
      *  Modify the image after decoding and scaling.
      *
@@ -1528,15 +1470,6 @@
         mMutable = mutable;
     }
 
-    /** @removed
-     * @deprecated Renamed to {@link #setMutableRequired}.
-     */
-    @Deprecated
-    public ImageDecoder setMutable(boolean mutable) {
-        this.setMutableRequired(mutable);
-        return this;
-    }
-
     /**
      *  Return whether the decoded {@link Bitmap} will be mutable.
      */
@@ -1544,14 +1477,6 @@
         return mMutable;
     }
 
-    /** @removed
-     * @deprecated Renamed to {@link #isMutableRequired}.
-     */
-    @Deprecated
-    public boolean getMutable() {
-        return this.isMutableRequired();
-    }
-
     /**
      * Save memory if possible by using a denser {@link Bitmap.Config} at the
      * cost of some image quality.
@@ -1597,22 +1522,6 @@
         return mConserveMemory ? MEMORY_POLICY_LOW_RAM : MEMORY_POLICY_DEFAULT;
     }
 
-    /** @removed
-     * @deprecated Replaced by {@link #setMemorySizePolicy}.
-     */
-    @Deprecated
-    public void setConserveMemory(boolean conserveMemory) {
-        mConserveMemory = conserveMemory;
-    }
-
-    /** @removed
-     * @deprecated Replaced by {@link #getMemorySizePolicy}.
-     */
-    @Deprecated
-    public boolean getConserveMemory() {
-        return mConserveMemory;
-    }
-
     /**
      *  Specify whether to potentially treat the output as an alpha mask.
      *
@@ -1632,24 +1541,6 @@
         mDecodeAsAlphaMask = enabled;
     }
 
-    /** @removed
-     * @deprecated Renamed to {@link #setDecodeAsAlphaMaskEnabled}.
-     */
-    @Deprecated
-    public ImageDecoder setDecodeAsAlphaMask(boolean enabled) {
-        this.setDecodeAsAlphaMaskEnabled(enabled);
-        return this;
-    }
-
-    /** @removed
-     * @deprecated Renamed to {@link #setDecodeAsAlphaMaskEnabled}.
-     */
-    @Deprecated
-    public ImageDecoder setAsAlphaMask(boolean asAlphaMask) {
-        this.setDecodeAsAlphaMask(asAlphaMask);
-        return this;
-    }
-
     /**
      *  Return whether to treat single channel input as alpha.
      *
@@ -1662,22 +1553,6 @@
         return mDecodeAsAlphaMask;
     }
 
-    /** @removed
-     * @deprecated Renamed to {@link #isDecodeAsAlphaMaskEnabled}.
-     */
-    @Deprecated
-    public boolean getDecodeAsAlphaMask() {
-        return mDecodeAsAlphaMask;
-    }
-
-    /** @removed
-     * @deprecated Renamed to {@link #isDecodeAsAlphaMaskEnabled}.
-     */
-    @Deprecated
-    public boolean getAsAlphaMask() {
-        return this.getDecodeAsAlphaMask();
-    }
-
     /**
      * Specify the desired {@link ColorSpace} for the output.
      *
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 53ec201..8511a21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
 
+import static com.android.input.flags.Flags.enablePointerChoreographer;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
@@ -63,6 +64,7 @@
 class DragResizeInputListener implements AutoCloseable {
     private static final String TAG = "DragResizeInputListener";
     private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
+    private final Context mContext;
     private final Handler mHandler;
     private final Choreographer mChoreographer;
     private final InputManager mInputManager;
@@ -110,6 +112,7 @@
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             DisplayController displayController) {
         mInputManager = context.getSystemService(InputManager.class);
+        mContext = context;
         mHandler = handler;
         mChoreographer = choreographer;
         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
@@ -451,7 +454,9 @@
                 }
                 case MotionEvent.ACTION_HOVER_ENTER:
                 case MotionEvent.ACTION_HOVER_MOVE: {
-                    updateCursorType(e.getXCursorPosition(), e.getYCursorPosition());
+                    updateCursorType(e.getDisplayId(), e.getDeviceId(),
+                            e.getPointerId(/*pointerIndex=*/0), e.getXCursorPosition(),
+                            e.getYCursorPosition());
                     result = true;
                     break;
                 }
@@ -579,7 +584,8 @@
             return 0;
         }
 
-        private void updateCursorType(float x, float y) {
+        private void updateCursorType(int displayId, int deviceId, int pointerId, float x,
+                float y) {
             @DragPositioningCallback.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
 
             int cursorType = PointerIcon.TYPE_DEFAULT;
@@ -611,9 +617,14 @@
             // where views in the task can receive input events because we can't set touch regions
             // of input sinks to have rounded corners.
             if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
-                mInputManager.setPointerIconType(cursorType);
+                if (enablePointerChoreographer()) {
+                    mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType),
+                            displayId, deviceId, pointerId, mInputChannel.getToken());
+                } else {
+                    mInputManager.setPointerIconType(cursorType);
+                }
                 mLastCursorType = cursorType;
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index fa07c39..a8b9633 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -65,9 +65,9 @@
     void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
                   BitSet32 spotIdBits, int32_t displayId) override;
     void clearSpots() override;
+    void updatePointerIcon(PointerIconStyle iconId) override;
+    void setCustomPointerIcon(const SpriteIcon& icon) override;
 
-    void updatePointerIcon(PointerIconStyle iconId);
-    void setCustomPointerIcon(const SpriteIcon& icon);
     virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
     void reloadPointerResources();
@@ -192,10 +192,10 @@
     void setPresentation(Presentation) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
-    void updatePointerIcon(PointerIconStyle) {
+    void updatePointerIcon(PointerIconStyle) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
-    void setCustomPointerIcon(const SpriteIcon&) {
+    void setCustomPointerIcon(const SpriteIcon&) override {
         LOG_ALWAYS_FATAL("Should not be called");
     }
     // fade() should not be called by inactivity timeout. Do nothing.
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index bf9419fe..4be282b 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -29,6 +29,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
@@ -330,7 +331,7 @@
      * @hide
      * Array of all usage types exposed in the SDK that applications can use.
      */
-    public final static int[] SDK_USAGES = {
+    public static final IntArray SDK_USAGES = IntArray.wrap(new int[] {
             USAGE_UNKNOWN,
             USAGE_MEDIA,
             USAGE_VOICE_COMMUNICATION,
@@ -347,14 +348,14 @@
             USAGE_ASSISTANCE_SONIFICATION,
             USAGE_GAME,
             USAGE_ASSISTANT,
-    };
+    });
 
     /**
      * @hide
      */
     @TestApi
     public static int[] getSdkUsages() {
-        return SDK_USAGES;
+        return SDK_USAGES.toArray();
     }
 
     /**
@@ -567,6 +568,15 @@
     private String mFormattedTags;
     private Bundle mBundle; // lazy-initialized, may be null
 
+    /** Array of all content types exposed in the SDK that applications can use */
+    private static final IntArray CONTENT_TYPES = IntArray.wrap(new int[]{
+            CONTENT_TYPE_UNKNOWN,
+            CONTENT_TYPE_SPEECH,
+            CONTENT_TYPE_MUSIC,
+            CONTENT_TYPE_MOVIE,
+            CONTENT_TYPE_SONIFICATION,
+    });
+
     private AudioAttributes() {
     }
 
@@ -1669,6 +1679,27 @@
     }
 
     /**
+     * Query if the usage is a valid sdk usage
+     *
+     * @param usage one of {@link AttributeSdkUsage}
+     * @return {@code true} if the usage is valid for sdk or {@code false} otherwise
+     * @hide
+     */
+    public static boolean isSdkUsage(@AttributeSdkUsage int usage) {
+        return SDK_USAGES.contains(usage);
+    }
+
+    /**
+     * Query if the content type is a valid sdk content type
+     * @param contentType one of {@link AttributeContentType}
+     * @return {@code true} if the content type is valid for sdk or {@code false} otherwise
+     * @hide
+     */
+    public static boolean isSdkContentType(@AttributeContentType int contentType) {
+        return CONTENT_TYPES.contains(contentType);
+    }
+
+    /**
      * Returns the stream type matching this {@code AudioAttributes} instance for volume control.
      * Use this method to derive the stream type needed to configure the volume
      * control slider in an {@link android.app.Activity} with
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 1e32349..627f73c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -20,6 +20,7 @@
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
 import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.audio.Flags.autoPublicVolumeApiHardening;
+import static android.media.audio.Flags.automaticBtDeviceType;
 import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
 
 import android.Manifest;
@@ -7170,10 +7171,14 @@
      * Sets the audio device type of a Bluetooth device given its MAC address
      */
     @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
-    public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle,
+    public void setBluetoothAudioDeviceCategory_legacy(@NonNull String address, boolean isBle,
             @AudioDeviceCategory int btAudioDeviceType) {
+        if (automaticBtDeviceType()) {
+            // do nothing
+            return;
+        }
         try {
-            getService().setBluetoothAudioDeviceCategory(address, isBle, btAudioDeviceType);
+            getService().setBluetoothAudioDeviceCategory_legacy(address, isBle, btAudioDeviceType);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -7185,9 +7190,67 @@
      */
     @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
     @AudioDeviceCategory
-    public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) {
+    public int getBluetoothAudioDeviceCategory_legacy(@NonNull String address, boolean isBle) {
+        if (automaticBtDeviceType()) {
+            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
         try {
-            return getService().getBluetoothAudioDeviceCategory(address, isBle);
+            return getService().getBluetoothAudioDeviceCategory_legacy(address, isBle);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Sets the audio device type of a Bluetooth device given its MAC address
+     *
+     * @return {@code true} if the device type was set successfully. If the
+     *         audio device type was automatically identified this method will
+     *         return {@code false}.
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public boolean setBluetoothAudioDeviceCategory(@NonNull String address,
+            @AudioDeviceCategory int btAudioDeviceCategory) {
+        if (!automaticBtDeviceType()) {
+            return false;
+        }
+        try {
+            return getService().setBluetoothAudioDeviceCategory(address, btAudioDeviceCategory);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Gets the audio device type of a Bluetooth device given its MAC address
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    @AudioDeviceCategory
+    public int getBluetoothAudioDeviceCategory(@NonNull String address) {
+        if (!automaticBtDeviceType()) {
+            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
+        try {
+            return getService().getBluetoothAudioDeviceCategory(address);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Returns {@code true} if the audio device type of a Bluetooth device can
+     * be automatically identified
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) {
+        if (!automaticBtDeviceType()) {
+            return false;
+        }
+        try {
+            return getService().isBluetoothAudioDeviceCategoryFixed(address);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 367b38a..9ffd644 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -318,11 +318,12 @@
 
     /**
      * @hide
-     * Convert a Bluetooth codec to an audio format enum
+     * Convert an A2DP Bluetooth codec to an audio format enum
      * @param btCodec the codec to convert.
      * @return the audio format, or {@link #AUDIO_FORMAT_DEFAULT} if unknown
      */
-    public static @AudioFormatNativeEnumForBtCodec int bluetoothCodecToAudioFormat(int btCodec) {
+    public static @AudioFormatNativeEnumForBtCodec int bluetoothA2dpCodecToAudioFormat(
+            int btCodec) {
         switch (btCodec) {
             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
                 return AudioSystem.AUDIO_FORMAT_SBC;
@@ -339,7 +340,25 @@
             case BluetoothCodecConfig.SOURCE_CODEC_TYPE_OPUS:
                 return AudioSystem.AUDIO_FORMAT_OPUS;
             default:
-                Log.e(TAG, "Unknown BT codec 0x" + Integer.toHexString(btCodec)
+                Log.e(TAG, "Unknown A2DP BT codec 0x" + Integer.toHexString(btCodec)
+                        + " for conversion to audio format");
+                // TODO returning DEFAULT is the current behavior, should this return INVALID?
+                return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+    }
+
+    /**
+     * @hide
+     * Convert a LE Audio Bluetooth codec to an audio format enum
+     * @param btCodec the codec to convert.
+     * @return the audio format, or {@link #AUDIO_FORMAT_DEFAULT} if unknown
+     */
+    public static @AudioFormatNativeEnumForBtCodec int bluetoothLeCodecToAudioFormat(int btCodec) {
+        switch (btCodec) {
+            case BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3:
+                return AudioSystem.AUDIO_FORMAT_LC3;
+            default:
+                Log.e(TAG, "Unknown LE Audio BT codec 0x" + Integer.toHexString(btCodec)
                         + " for conversion to audio format");
                 // TODO returning DEFAULT is the current behavior, should this return INVALID?
                 return AudioSystem.AUDIO_FORMAT_DEFAULT;
diff --git a/media/java/android/media/FadeManagerConfiguration.aidl b/media/java/android/media/FadeManagerConfiguration.aidl
new file mode 100644
index 0000000..ceb4ded
--- /dev/null
+++ b/media/java/android/media/FadeManagerConfiguration.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * Class to encapsulate fade configurations.
+ * @hide
+ */
+parcelable FadeManagerConfiguration;
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
new file mode 100644
index 0000000..337d4b0
--- /dev/null
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -0,0 +1,1684 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.IntArray;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Class to encapsulate fade configurations.
+ *
+ * <p>Configurations are provided through:
+ * <ul>
+ *     <li>Fadeable list: a positive list of fadeable type - usage</li>
+ *     <li>Unfadeable lists: negative list of unfadeable types - content type, uid, audio attributes
+ *     </li>
+ *     <li>Volume shaper configs: fade in and fade out configs per usage or audio attributes
+ *     </li>
+ * </ul>
+ *
+ * <p>Fade manager configuration can be created in one of the following ways:
+ * <ul>
+ *     <li>Disabled fades:
+ *     <pre class="prettyprint">
+ *         new FadeManagerConfiguration.Builder()
+ *               .setFadeState(FADE_STATE_DISABLED).build()
+ *               </pre>
+ *     Can be used to disable fading</li>
+ *     <li>Default configurations including default fade duration:
+ *     <pre class="prettyprint">
+ *         new FadeManagerConfiguration.Builder()
+ *                .setFadeState(FADE_STATE_ENABLED_DEFAULT).build()
+ *                </pre>
+ *     Can be used to enable default fading configurations</li>
+ *     <li>Default configurations with custom fade duration:
+ *     <pre class="prettyprint">
+ *         new FadeManagerConfiguration.Builder(fade out duration, fade in duration)
+ *            .setFadeState(FADE_STATE_ENABLED_DEFAULT).build()
+ *            </pre>
+ *     Can be used to enable default fadeability lists with configurable fade in and out duration
+ *     </li>
+ *     <li>Custom configurations and fade volume shapers:
+ *     <pre class="prettyprint">
+ *         new FadeManagerConfiguration.Builder(fade out duration, fade in duration)
+ *                .setFadeState(FADE_STATE_ENABLED_DEFAULT)
+ *                .setFadeableUsages(list of usages)
+ *                .setUnfadeableContentTypes(list of content types)
+ *                .setUnfadeableUids(list of uids)
+ *                .setUnfadeableAudioAttributes(list of audio attributes)
+ *                .setFadeOutVolumeShaperConfigForAudioAttributes(attributes, volume shaper config)
+ *                .setFadeInDurationForUsaeg(usage, duration)
+ *                ....
+ *                .build() </pre>
+ *      Achieves full customization of fadeability lists and configurations</li>
+ *      <li>Also provides a copy constructor from another instance of fade manager configuration
+ *      <pre class="prettyprint">
+ *          new FadeManagerConfiguration.Builder(fadeManagerConfiguration)
+ *                 .addFadeableUsage(new usage)
+ *                 ....
+ *                 .build()</pre>
+ *      Helps with recreating a new instance from another to simply change/add on top of the
+ *      existing ones</li>
+ * </ul>
+ * TODO(b/304835727): Convert into system API so that it can be set through AudioPolicy
+ *
+ * @hide
+ */
+
+@FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+public final class FadeManagerConfiguration implements Parcelable {
+
+    public static final String TAG = "FadeManagerConfiguration";
+
+    /**
+     * Defines the disabled fade state. No player will be faded in this state.
+     */
+    public static final int FADE_STATE_DISABLED = 0;
+
+    /**
+     * Defines the enabled fade state with default configurations
+     */
+    public static final int FADE_STATE_ENABLED_DEFAULT = 1;
+
+    /**
+     * Defines the enabled state with Automotive specific configurations
+     */
+    public static final int FADE_STATE_ENABLED_AUTO = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "FADE_STATE", value = {
+            FADE_STATE_DISABLED,
+            FADE_STATE_ENABLED_DEFAULT,
+            FADE_STATE_ENABLED_AUTO,
+    })
+    public @interface FadeStateEnum {}
+
+    /**
+     * Defines ID to be used in volume shaper for fading
+     */
+    public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2;
+
+    /**
+     * Used to reset duration or return duration when not set
+     *
+     * @see Builder#setFadeOutDurationForUsage(int, long)
+     * @see Builder#setFadeInDurationForUsage(int, long)
+     * @see Builder#setFadeOutDurationForAudioAttributes(AudioAttributes, long)
+     * @see Builder#setFadeInDurationForAudioAttributes(AudioAttributes, long)
+     * @see #getFadeOutDurationForUsage(int)
+     * @see #getFadeInDurationForUsage(int)
+     * @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
+     * @see #getFadeInDurationForAudioAttributes(AudioAttributes)
+     */
+    public static final long DURATION_NOT_SET = 0;
+    /** Map of Usage to Fade volume shaper configs wrapper */
+    private final SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap;
+    /** Map of AudioAttributes to Fade volume shaper configs wrapper */
+    private final ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> mAttrToFadeWrapperMap;
+    /** list of fadeable usages */
+    private final @NonNull IntArray mFadeableUsages;
+    /** list of unfadeable content types */
+    private final @NonNull IntArray mUnfadeableContentTypes;
+    /** list of unfadeable player types */
+    private final @NonNull IntArray mUnfadeablePlayerTypes;
+    /** list of unfadeable uid(s) */
+    private final @NonNull IntArray mUnfadeableUids;
+    /** list of unfadeable AudioAttributes */
+    private final @NonNull List<AudioAttributes> mUnfadeableAudioAttributes;
+    /** fade state */
+    private final @FadeStateEnum int mFadeState;
+    /** fade out duration from builder - used for creating default fade out volume shaper */
+    private final long mFadeOutDurationMillis;
+    /** fade in duration from builder - used for creating default fade in volume shaper */
+    private final long mFadeInDurationMillis;
+    /** delay after which the offending players are faded back in */
+    private final long mFadeInDelayForOffendersMillis;
+
+    private FadeManagerConfiguration(int fadeState, long fadeOutDurationMillis,
+            long fadeInDurationMillis, long offendersFadeInDelayMillis,
+            @NonNull SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap,
+            @NonNull ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> attrToFadeWrapperMap,
+            @NonNull IntArray fadeableUsages, @NonNull IntArray unfadeableContentTypes,
+            @NonNull IntArray unfadeablePlayerTypes, @NonNull IntArray unfadeableUids,
+            @NonNull List<AudioAttributes> unfadeableAudioAttributes) {
+        mFadeState = fadeState;
+        mFadeOutDurationMillis = fadeOutDurationMillis;
+        mFadeInDurationMillis = fadeInDurationMillis;
+        mFadeInDelayForOffendersMillis = offendersFadeInDelayMillis;
+        mUsageToFadeWrapperMap = Objects.requireNonNull(usageToFadeWrapperMap,
+                "Usage to fade wrapper map cannot be null");
+        mAttrToFadeWrapperMap = Objects.requireNonNull(attrToFadeWrapperMap,
+                "Attribute to fade wrapper map cannot be null");
+        mFadeableUsages = Objects.requireNonNull(fadeableUsages,
+                "List of fadeable usages cannot be null");
+        mUnfadeableContentTypes = Objects.requireNonNull(unfadeableContentTypes,
+                "List of unfadeable content types cannot be null");
+        mUnfadeablePlayerTypes = Objects.requireNonNull(unfadeablePlayerTypes,
+                "List of unfadeable player types cannot be null");
+        mUnfadeableUids = Objects.requireNonNull(unfadeableUids,
+                "List of unfadeable uids cannot be null");
+        mUnfadeableAudioAttributes = Objects.requireNonNull(unfadeableAudioAttributes,
+                "List of unfadeable audio attributes cannot be null");
+    }
+
+    /**
+     * Get the fade state
+     *
+     * @return one of the {@link FadeStateEnum} state
+     */
+    @FadeStateEnum
+    public int getFadeState() {
+        return mFadeState;
+    }
+
+    /**
+     * Get the list of usages that can be faded
+     *
+     * @return list of {@link android.media.AudioAttributes.AttributeUsage} that shall be faded
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    @NonNull
+    public List<Integer> getFadeableUsages() {
+        ensureFadingIsEnabled();
+        return convertIntArrayToIntegerList(mFadeableUsages);
+    }
+
+    /**
+     * Get the list of {@link android.media.AudioPlaybackConfiguration.PlayerType player types}
+     * that cannot be faded
+     *
+     * @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    @NonNull
+    public List<Integer> getUnfadeablePlayerTypes() {
+        ensureFadingIsEnabled();
+        return convertIntArrayToIntegerList(mUnfadeablePlayerTypes);
+    }
+
+    /**
+     * Get the list of {@link android.media.AudioAttributes.AttributeContentType content types}
+     * that cannot be faded
+     *
+     * @return list of {@link android.media.AudioAttributes.AttributeContentType}
+     * @throws IllegalStateExceptionif if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    @NonNull
+    public List<Integer> getUnfadeableContentTypes() {
+        ensureFadingIsEnabled();
+        return convertIntArrayToIntegerList(mUnfadeableContentTypes);
+    }
+
+    /**
+     * Get the list of uids that cannot be faded
+     *
+     * @return list of uids that shall not be faded
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    @NonNull
+    public List<Integer> getUnfadeableUids() {
+        ensureFadingIsEnabled();
+        return convertIntArrayToIntegerList(mUnfadeableUids);
+    }
+
+    /**
+     * Get the list of {@link android.media.AudioAttributes} that cannot be faded
+     *
+     * @return list of {@link android.media.AudioAttributes} that shall not be faded
+     * @throws IllegalStateException if fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    @NonNull
+    public List<AudioAttributes> getUnfadeableAudioAttributes() {
+        ensureFadingIsEnabled();
+        return mUnfadeableAudioAttributes;
+    }
+
+    /**
+     * Get the duration used to fade out players with
+     * {@link android.media.AudioAttributes.AttributeUsage}
+     *
+     * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+     * @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
+     * @throws IllegalArgumentException if the usage is invalid
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    public long getFadeOutDurationForUsage(int usage) {
+        ensureFadingIsEnabled();
+        validateUsage(usage);
+        return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
+                mUsageToFadeWrapperMap.get(usage), /* isFadeIn= */ false));
+    }
+
+    /**
+     * Get the duration used to fade in players with
+     * {@link android.media.AudioAttributes.AttributeUsage}
+     *
+     * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+     * @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
+     * @throws IllegalArgumentException if the usage is invalid
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    public long getFadeInDurationForUsage(int usage) {
+        ensureFadingIsEnabled();
+        validateUsage(usage);
+        return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
+                mUsageToFadeWrapperMap.get(usage), /* isFadeIn= */ true));
+    }
+
+    /**
+     * Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
+     * {@link android.media.AudioAttributes.AttributeUsage}
+     *
+     * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+     * @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
+     * {@code null} otherwise
+     * @throws IllegalArgumentException if the usage is invalid
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    @Nullable
+    public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(int usage) {
+        ensureFadingIsEnabled();
+        validateUsage(usage);
+        return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
+                /* isFadeIn= */ false);
+    }
+
+    /**
+     * Get the {@link android.media.VolumeShaper.Configuration} used to fade in players with
+     * {@link android.media.AudioAttributes.AttributeUsage}
+     *
+     * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of player
+     * @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
+     * {@code null} otherwise
+     * @throws IllegalArgumentException if the usage is invalid
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    @Nullable
+    public VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(int usage) {
+        ensureFadingIsEnabled();
+        validateUsage(usage);
+        return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
+                /* isFadeIn= */ true);
+    }
+
+    /**
+     * Get the duration used to fade out players with {@link android.media.AudioAttributes}
+     *
+     * @param audioAttributes {@link android.media.AudioAttributes}
+     * @return duration in milliseconds if set for the audio attributes or
+     * {@link #DURATION_NOT_SET} otherwise
+     * @throws NullPointerException if the audio attributes is {@code null}
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    public long getFadeOutDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
+        ensureFadingIsEnabled();
+        return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
+                mAttrToFadeWrapperMap.get(audioAttributes), /* isFadeIn= */ false));
+    }
+
+    /**
+     * Get the duration used to fade-in players with {@link android.media.AudioAttributes}
+     *
+     * @param audioAttributes {@link android.media.AudioAttributes}
+     * @return duration in milliseconds if set for the audio attributes or
+     * {@link #DURATION_NOT_SET} otherwise
+     * @throws NullPointerException if the audio attributes is {@code null}
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    public long getFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
+        ensureFadingIsEnabled();
+        return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
+                mAttrToFadeWrapperMap.get(audioAttributes), /* isFadeIn= */ true));
+    }
+
+    /**
+     * Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
+     * {@link android.media.AudioAttributes}
+     *
+     * @param audioAttributes {@link android.media.AudioAttributes}
+     * @return {@link android.media.VolumeShaper.Configuration} if set for the audio attribute or
+     * {@code null} otherwise
+     * @throws NullPointerException if the audio attributes is {@code null}
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    @Nullable
+    public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForAudioAttributes(
+            @NonNull AudioAttributes audioAttributes) {
+        Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
+        ensureFadingIsEnabled();
+        return getVolumeShaperConfigFromWrapper(mAttrToFadeWrapperMap.get(audioAttributes),
+                /* isFadeIn= */ false);
+    }
+
+    /**
+     * Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
+     * {@link android.media.AudioAttributes}
+     *
+     * @param audioAttributes {@link android.media.AudioAttributes}
+     * @return {@link android.media.VolumeShaper.Configuration} used for fading in if set for the
+     * audio attribute or {@code null} otherwise
+     * @throws NullPointerException if the audio attributes is {@code null}
+     * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
+     */
+    @Nullable
+    public VolumeShaper.Configuration getFadeInVolumeShaperConfigForAudioAttributes(
+            @NonNull AudioAttributes audioAttributes) {
+        Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
+        ensureFadingIsEnabled();
+        return getVolumeShaperConfigFromWrapper(mAttrToFadeWrapperMap.get(audioAttributes),
+                /* isFadeIn= */ true);
+    }
+
+    /**
+     * Get the list of {@link android.media.AudioAttributes} for whome the volume shaper
+     * configurations are defined
+     *
+     * @return list of {@link android.media.AudioAttributes} with valid volume shaper configs or
+     * empty list if none set.
+     */
+    @NonNull
+    public List<AudioAttributes> getAudioAttributesWithVolumeShaperConfigs() {
+        return getAudioAttributesInternal();
+    }
+
+    /**
+     * Get the delay after which the offending players are faded back in
+     *
+     * @return delay in milliseconds
+     */
+    public long getFadeInDelayForOffenders() {
+        return mFadeInDelayForOffendersMillis;
+    }
+
+    /**
+     * Query if fade is enabled
+     *
+     * @return {@code true} if fading is enabled, {@code false} otherwise
+     */
+    public boolean isFadeEnabled() {
+        return mFadeState != FADE_STATE_DISABLED;
+    }
+
+    /**
+     * Query if the usage is fadeable
+     *
+     * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+     * @return {@code true} if usage is fadeable, {@code false} otherwise
+     */
+    public boolean isUsageFadeable(@AudioAttributes.AttributeUsage int usage) {
+        if (!isFadeEnabled()) {
+            return false;
+        }
+        return mFadeableUsages.contains(usage);
+    }
+
+    /**
+     * Query if the content type is unfadeable
+     *
+     * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+     * @return {@code true} if content type is unfadeable or if fade state is set to
+     * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+     */
+    public boolean isContentTypeUnfadeable(@AudioAttributes.AttributeContentType int contentType) {
+        if (!isFadeEnabled()) {
+            return true;
+        }
+        return mUnfadeableContentTypes.contains(contentType);
+    }
+
+    /**
+     * Query if the player type is unfadeable
+     *
+     * @param playerType the {@link android.media.AudioPlaybackConfiguration} player type
+     * @return {@code true} if player type is unfadeable or if fade state is set to
+     * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+     */
+    public boolean isPlayerTypeUnfadeable(int playerType) {
+        if (!isFadeEnabled()) {
+            return true;
+        }
+        return mUnfadeablePlayerTypes.contains(playerType);
+    }
+
+    /**
+     * Query if the audio attributes is unfadeable
+     *
+     * @param audioAttributes the {@link android.media.AudioAttributes}
+     * @return {@code true} if audio attributes is unfadeable or if fade state is set to
+     * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+     * @throws NullPointerException if the audio attributes is {@code null}
+     */
+    public boolean isAudioAttributesUnfadeable(@NonNull AudioAttributes audioAttributes) {
+        Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
+        if (!isFadeEnabled()) {
+            return true;
+        }
+        return mUnfadeableAudioAttributes.contains(audioAttributes);
+    }
+
+    /**
+     * Query if the uid is unfadeable
+     *
+     * @param uid the uid of application
+     * @return {@code true} if uid is unfadeable or if fade state is set to
+     * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+     */
+    public boolean isUidUnfadeable(int uid) {
+        if (!isFadeEnabled()) {
+            return true;
+        }
+        return mUnfadeableUids.contains(uid);
+    }
+
+    @Override
+    public String toString() {
+        return "FadeManagerConfiguration { fade state = " + fadeStateToString(mFadeState)
+                + ", fade out duration = " + mFadeOutDurationMillis
+                + ", fade in duration = " + mFadeInDurationMillis
+                + ", offenders fade in delay = " + mFadeInDelayForOffendersMillis
+                + ", fade volume shapers for audio attributes = " + mAttrToFadeWrapperMap
+                + ", fadeable usages = " + mFadeableUsages.toString()
+                + ", unfadeable content types = " + mUnfadeableContentTypes.toString()
+                + ", unfadeable player types = " + mUnfadeablePlayerTypes.toString()
+                + ", unfadeable uids = " + mUnfadeableUids.toString()
+                + ", unfadeable audio attributes = " + mUnfadeableAudioAttributes + "}";
+    }
+
+    /**
+     * Convert fade state into a human-readable string
+     *
+     * @param fadeState one of the fade state in {@link FadeStateEnum}
+     * @return human-readable string
+     */
+    @NonNull
+    public static String fadeStateToString(@FadeStateEnum int fadeState) {
+        switch (fadeState) {
+            case FADE_STATE_DISABLED:
+                return "FADE_STATE_DISABLED";
+            case FADE_STATE_ENABLED_DEFAULT:
+                return "FADE_STATE_ENABLED_DEFAULT";
+            case FADE_STATE_ENABLED_AUTO:
+                return "FADE_STATE_ENABLED_AUTO";
+            default:
+                return "unknown fade state: " + fadeState;
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof FadeManagerConfiguration)) {
+            return false;
+        }
+
+        FadeManagerConfiguration rhs = (FadeManagerConfiguration) o;
+
+        return mUsageToFadeWrapperMap.contentEquals(rhs.mUsageToFadeWrapperMap)
+                && mAttrToFadeWrapperMap.equals(rhs.mAttrToFadeWrapperMap)
+                && Arrays.equals(mFadeableUsages.toArray(), rhs.mFadeableUsages.toArray())
+                && Arrays.equals(mUnfadeableContentTypes.toArray(),
+                rhs.mUnfadeableContentTypes.toArray())
+                && Arrays.equals(mUnfadeablePlayerTypes.toArray(),
+                rhs.mUnfadeablePlayerTypes.toArray())
+                && Arrays.equals(mUnfadeableUids.toArray(), rhs.mUnfadeableUids.toArray())
+                && mUnfadeableAudioAttributes.equals(rhs.mUnfadeableAudioAttributes)
+                && mFadeState == rhs.mFadeState
+                && mFadeOutDurationMillis == rhs.mFadeOutDurationMillis
+                && mFadeInDurationMillis == rhs.mFadeInDurationMillis
+                && mFadeInDelayForOffendersMillis == rhs.mFadeInDelayForOffendersMillis;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUsageToFadeWrapperMap, mAttrToFadeWrapperMap, mFadeableUsages,
+                mUnfadeableContentTypes, mUnfadeablePlayerTypes, mUnfadeableAudioAttributes,
+                mUnfadeableUids, mFadeState, mFadeOutDurationMillis, mFadeInDurationMillis,
+                mFadeInDelayForOffendersMillis);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mFadeState);
+        dest.writeLong(mFadeOutDurationMillis);
+        dest.writeLong(mFadeInDurationMillis);
+        dest.writeLong(mFadeInDelayForOffendersMillis);
+        dest.writeTypedSparseArray(mUsageToFadeWrapperMap, flags);
+        dest.writeMap(mAttrToFadeWrapperMap);
+        dest.writeIntArray(mFadeableUsages.toArray());
+        dest.writeIntArray(mUnfadeableContentTypes.toArray());
+        dest.writeIntArray(mUnfadeablePlayerTypes.toArray());
+        dest.writeIntArray(mUnfadeableUids.toArray());
+        dest.writeTypedList(mUnfadeableAudioAttributes, flags);
+    }
+
+    /**
+     * Creates fade manage configuration from parcel
+     *
+     * @hide
+     */
+    @VisibleForTesting()
+    FadeManagerConfiguration(Parcel in) {
+        int fadeState = in.readInt();
+        long fadeOutDurationMillis = in.readLong();
+        long fadeInDurationMillis = in.readLong();
+        long fadeInDelayForOffenders = in.readLong();
+        SparseArray<FadeVolumeShaperConfigsWrapper> usageToWrapperMap =
+                in.createTypedSparseArray(FadeVolumeShaperConfigsWrapper.CREATOR);
+        ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> attrToFadeWrapperMap =
+                new ArrayMap<>();
+        in.readMap(attrToFadeWrapperMap, getClass().getClassLoader(), AudioAttributes.class,
+                FadeVolumeShaperConfigsWrapper.class);
+        int[] fadeableUsages = in.createIntArray();
+        int[] unfadeableContentTypes = in.createIntArray();
+        int[] unfadeablePlayerTypes = in.createIntArray();
+        int[] unfadeableUids = in.createIntArray();
+        List<AudioAttributes> unfadeableAudioAttributes = new ArrayList<>();
+        in.readTypedList(unfadeableAudioAttributes, AudioAttributes.CREATOR);
+
+        this.mFadeState = fadeState;
+        this.mFadeOutDurationMillis = fadeOutDurationMillis;
+        this.mFadeInDurationMillis = fadeInDurationMillis;
+        this.mFadeInDelayForOffendersMillis = fadeInDelayForOffenders;
+        this.mUsageToFadeWrapperMap = usageToWrapperMap;
+        this.mAttrToFadeWrapperMap = attrToFadeWrapperMap;
+        this.mFadeableUsages = IntArray.wrap(fadeableUsages);
+        this.mUnfadeableContentTypes = IntArray.wrap(unfadeableContentTypes);
+        this.mUnfadeablePlayerTypes = IntArray.wrap(unfadeablePlayerTypes);
+        this.mUnfadeableUids = IntArray.wrap(unfadeableUids);
+        this.mUnfadeableAudioAttributes = unfadeableAudioAttributes;
+    }
+
+    @NonNull
+    public static final Creator<FadeManagerConfiguration> CREATOR = new Creator<>() {
+        @Override
+        @NonNull
+        public FadeManagerConfiguration createFromParcel(@NonNull Parcel in) {
+            return new FadeManagerConfiguration(in);
+        }
+
+        @Override
+        @NonNull
+        public FadeManagerConfiguration[] newArray(int size) {
+            return new FadeManagerConfiguration[size];
+        }
+    };
+
+    private long getDurationForVolumeShaperConfig(VolumeShaper.Configuration config) {
+        return config != null ? config.getDuration() : DURATION_NOT_SET;
+    }
+
+    private VolumeShaper.Configuration getVolumeShaperConfigFromWrapper(
+            FadeVolumeShaperConfigsWrapper wrapper, boolean isFadeIn) {
+        // if no volume shaper config is available, return null
+        if (wrapper == null) {
+            return null;
+        }
+        if (isFadeIn) {
+            return wrapper.getFadeInVolShaperConfig();
+        }
+        return wrapper.getFadeOutVolShaperConfig();
+    }
+
+    private List<AudioAttributes> getAudioAttributesInternal() {
+        List<AudioAttributes> attrs = new ArrayList<>(mAttrToFadeWrapperMap.size());
+        for (int index = 0; index < mAttrToFadeWrapperMap.size(); index++) {
+            attrs.add(mAttrToFadeWrapperMap.keyAt(index));
+        }
+        return attrs;
+    }
+
+    private static boolean isUsageValid(int usage) {
+        return AudioAttributes.isSdkUsage(usage) || AudioAttributes.isSystemUsage(usage);
+    }
+
+    private void ensureFadingIsEnabled() {
+        if (!isFadeEnabled()) {
+            throw new IllegalStateException("Method call not allowed when fade is disabled");
+        }
+    }
+
+    private static void validateUsage(int usage) {
+        Preconditions.checkArgument(isUsageValid(usage), "Invalid usage: %s", usage);
+    }
+
+    private static IntArray convertIntegerListToIntArray(List<Integer> integerList) {
+        if (integerList == null) {
+            return new IntArray();
+        }
+
+        IntArray intArray = new IntArray(integerList.size());
+        for (int index = 0; index < integerList.size(); index++) {
+            intArray.add(integerList.get(index));
+        }
+        return intArray;
+    }
+
+    private static List<Integer> convertIntArrayToIntegerList(IntArray intArray) {
+        if (intArray == null) {
+            return new ArrayList<>();
+        }
+
+        ArrayList<Integer> integerArrayList = new ArrayList<>(intArray.size());
+        for (int index = 0; index < intArray.size(); index++) {
+            integerArrayList.add(intArray.get(index));
+        }
+        return integerArrayList;
+    }
+
+    /**
+     * Builder class for {@link FadeManagerConfiguration} objects.
+     *
+     * <p><b>Notes:</b>
+     * <ul>
+     *     <li>When fade state is set to enabled, the builder expects at least one valid usage to be
+     *     set/added. Failure to do so will result in an exception during {@link #build()}</li>
+     *     <li>Every usage added to the fadeable list should have corresponding volume shaper
+     *     configs defined. This can be achieved by setting either the duration or volume shaper
+     *     config through {@link #setFadeOutDurationForUsage(int, long)} or
+     *     {@link #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)}</li>
+     *     <li> It is recommended to set volume shaper configurations individually for fade out and
+     *     fade in</li>
+     *     <li>For any incomplete volume shaper configs a volume shaper configuration will be
+     *     created using either the default fade durations or the ones provided as part of the
+     *     {@link #Builder(long, long)}</li>
+     *     <li>Additional volume shaper configs can also configured for a given usage
+     *     with additional attributes like content-type in order to achieve finer fade controls.
+     *     See:
+     *     {@link #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
+     *     VolumeShaper.Configuration)} and
+     *     {@link #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
+     *     VolumeShaper.Configuration)} </li>
+     *     </ul>
+     *
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static final class Builder {
+        private static final int INVALID_INDEX = -1;
+        private static final long IS_BUILDER_USED_FIELD_SET = 1 << 0;
+        private static final long IS_FADEABLE_USAGES_FIELD_SET = 1 << 1;
+        private static final long IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET = 1 << 2;
+
+        /** duration of the fade out curve */
+        private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+        /** duration of the fade in curve */
+        private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
+
+        /**
+         * delay after which a faded out player will be faded back in. This will be heard by the
+         * user only in the case of unmuting players that didn't respect audio focus and didn't
+         * stop/pause when their app lost focus.
+         * This is the amount of time between the app being notified of the focus loss
+         * (when its muted by the fade out), and the time fade in (to unmute) starts
+         */
+        private static final long DEFAULT_DELAY_FADE_IN_OFFENDERS_MS = 2_000;
+
+
+        private static final IntArray DEFAULT_UNFADEABLE_PLAYER_TYPES = IntArray.wrap(new int[]{
+                AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
+                AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
+        });
+
+        private static final IntArray DEFAULT_UNFADEABLE_CONTENT_TYPES = IntArray.wrap(new int[]{
+                AudioAttributes.CONTENT_TYPE_SPEECH
+        });
+
+        private static final IntArray DEFAULT_FADEABLE_USAGES = IntArray.wrap(new int[]{
+                AudioAttributes.USAGE_GAME,
+                AudioAttributes.USAGE_MEDIA
+        });
+
+        private int mFadeState = FADE_STATE_ENABLED_DEFAULT;
+        private long mFadeInDelayForOffendersMillis = DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+        private long mFadeOutDurationMillis;
+        private long mFadeInDurationMillis;
+        private long mBuilderFieldsSet;
+        private SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap =
+                new SparseArray<>();
+        private ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> mAttrToFadeWrapperMap =
+                new ArrayMap<>();
+        private IntArray mFadeableUsages = new IntArray();
+        private IntArray mUnfadeableContentTypes = new IntArray();
+        // Player types are not yet configurable
+        private IntArray mUnfadeablePlayerTypes = DEFAULT_UNFADEABLE_PLAYER_TYPES;
+        private IntArray mUnfadeableUids = new IntArray();
+        private List<AudioAttributes> mUnfadeableAudioAttributes = new ArrayList<>();
+
+        /**
+         * Constructs a new Builder with default fade out and fade in durations
+         */
+        public Builder() {
+            mFadeOutDurationMillis = DEFAULT_FADE_OUT_DURATION_MS;
+            mFadeInDurationMillis = DEFAULT_FADE_IN_DURATION_MS;
+        }
+
+        /**
+         * Constructs a new Builder with the provided fade out and fade in durations
+         *
+         * @param fadeOutDurationMillis duration in milliseconds used for fading out
+         * @param fadeInDurationMills duration in milliseconds used for fading in
+         */
+        public Builder(long fadeOutDurationMillis, long fadeInDurationMills) {
+            mFadeOutDurationMillis = fadeOutDurationMillis;
+            mFadeInDurationMillis = fadeInDurationMills;
+        }
+
+        /**
+         * Constructs a new Builder from the given {@link FadeManagerConfiguration}
+         *
+         * @param fmc the {@link FadeManagerConfiguration} object whose data will be reused in the
+         *            new builder
+         */
+        public Builder(@NonNull FadeManagerConfiguration fmc) {
+            mFadeState = fmc.mFadeState;
+            mUsageToFadeWrapperMap = fmc.mUsageToFadeWrapperMap.clone();
+            mAttrToFadeWrapperMap = new ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper>(
+                    fmc.mAttrToFadeWrapperMap);
+            mFadeableUsages = fmc.mFadeableUsages.clone();
+            setFlag(IS_FADEABLE_USAGES_FIELD_SET);
+            mUnfadeableContentTypes = fmc.mUnfadeableContentTypes.clone();
+            setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
+            mUnfadeablePlayerTypes = fmc.mUnfadeablePlayerTypes.clone();
+            mUnfadeableUids = fmc.mUnfadeableUids.clone();
+            mUnfadeableAudioAttributes = new ArrayList<>(fmc.mUnfadeableAudioAttributes);
+            mFadeOutDurationMillis = fmc.mFadeOutDurationMillis;
+            mFadeInDurationMillis = fmc.mFadeInDurationMillis;
+        }
+
+        /**
+         * Set the overall fade state
+         *
+         * @param state one of the {@link FadeStateEnum} states
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the fade state is invalid
+         * @see #getFadeState()
+         */
+        @NonNull
+        public Builder setFadeState(@FadeStateEnum int state) {
+            validateFadeState(state);
+            mFadeState = state;
+            return this;
+        }
+
+        /**
+         * Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
+         * {@link android.media.AudioAttributes.AttributeUsage}
+         * <p>
+         * This method accepts {@code null} for volume shaper config to clear a previously set
+         * configuration (example, if set through
+         * {@link #Builder(android.media.FadeManagerConfiguration)})
+         *
+         * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player
+         * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
+         *                             to fade out players with usage
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the usage is invalid
+         * @see #getFadeOutVolumeShaperConfigForUsage(int)
+         */
+        @NonNull
+        public Builder setFadeOutVolumeShaperConfigForUsage(int usage,
+                @Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
+            validateUsage(usage);
+            getFadeVolShaperConfigWrapperForUsage(usage)
+                    .setFadeOutVolShaperConfig(fadeOutVShaperConfig);
+            cleanupInactiveWrapperEntries(usage);
+            return this;
+        }
+
+        /**
+         * Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
+         * {@link android.media.AudioAttributes.AttributeUsage}
+         * <p>
+         * This method accepts {@code null} for volume shaper config to clear a previously set
+         * configuration (example, if set through
+         * {@link #Builder(android.media.FadeManagerConfiguration)})
+         *
+         * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+         * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
+         *                            to fade in players with usage
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the usage is invalid
+         * @see #getFadeInVolumeShaperConfigForUsage(int)
+         */
+        @NonNull
+        public Builder setFadeInVolumeShaperConfigForUsage(int usage,
+                @Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
+            validateUsage(usage);
+            getFadeVolShaperConfigWrapperForUsage(usage)
+                    .setFadeInVolShaperConfig(fadeInVShaperConfig);
+            cleanupInactiveWrapperEntries(usage);
+            return this;
+        }
+
+        /**
+         * Set the duration used for fading out players with
+         * {@link android.media.AudioAttributes.AttributeUsage}
+         * <p>
+         * A Volume shaper configuration is generated with the provided duration and default
+         * volume curve definitions. This config is then used to fade out players with given usage.
+         * <p>
+         * In order to clear previously set duration (example, if set through
+         * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
+         * {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
+         * {@code null}
+         *
+         * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player
+         * @param fadeOutDurationMillis positive duration in milliseconds or
+         * {@link #DURATION_NOT_SET}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the fade out duration is non-positive with the
+         * exception of {@link #DURATION_NOT_SET}
+         * @see #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
+         * @see #getFadeOutDurationForUsage(int)
+         */
+        @NonNull
+        public Builder setFadeOutDurationForUsage(int usage,  long fadeOutDurationMillis) {
+            validateUsage(usage);
+            VolumeShaper.Configuration fadeOutVShaperConfig =
+                    createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
+            setFadeOutVolumeShaperConfigForUsage(usage, fadeOutVShaperConfig);
+            return this;
+        }
+
+        /**
+         * Set the duration used for fading in players with
+         * {@link android.media.AudioAttributes.AttributeUsage}
+         * <p>
+         * A Volume shaper configuration is generated with the provided duration and default
+         * volume curve definitions. This config is then used to fade in players with given usage.
+         * <p>
+         * <b>Note: </b>In order to clear previously set duration (example, if set through
+         * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
+         * {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
+         * {@code null}
+         *
+         * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player
+         * @param fadeInDurationMillis positive duration in milliseconds or
+         * {@link #DURATION_NOT_SET}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the fade in duration is non-positive with the
+         * exception of {@link #DURATION_NOT_SET}
+         * @see #setFadeInVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
+         * @see #getFadeInDurationForUsage(int)
+         */
+        @NonNull
+        public Builder setFadeInDurationForUsage(int usage,  long fadeInDurationMillis) {
+            validateUsage(usage);
+            VolumeShaper.Configuration fadeInVShaperConfig =
+                    createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
+            setFadeInVolumeShaperConfigForUsage(usage, fadeInVShaperConfig);
+            return this;
+        }
+
+        /**
+         * Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
+         * {@link android.media.AudioAttributes}
+         * <p>
+         * This method accepts {@code null} for volume shaper config to clear a previously set
+         * configuration (example, set through
+         * {@link #Builder(android.media.FadeManagerConfiguration)})
+         *
+         * @param audioAttributes the {@link android.media.AudioAttributes}
+         * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
+         *                             fade out players with audio attribute
+         * @return the same Builder instance
+         * @throws NullPointerException if the audio attributes is {@code null}
+         * @see #getFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes)
+         */
+        @NonNull
+        public Builder setFadeOutVolumeShaperConfigForAudioAttributes(
+                @NonNull AudioAttributes audioAttributes,
+                @Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
+            Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
+            getFadeVolShaperConfigWrapperForAttr(audioAttributes)
+                    .setFadeOutVolShaperConfig(fadeOutVShaperConfig);
+            cleanupInactiveWrapperEntries(audioAttributes);
+            return this;
+        }
+
+        /**
+         * Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
+         * {@link android.media.AudioAttributes}
+         *
+         * <p>This method accepts {@code null} for volume shaper config to clear a previously set
+         * configuration (example, set through
+         * {@link #Builder(android.media.FadeManagerConfiguration)})
+         *
+         * @param audioAttributes the {@link android.media.AudioAttributes}
+         * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
+         *                            fade in players with audio attribute
+         * @return the same Builder instance
+         * @throws NullPointerException if the audio attributes is {@code null}
+         * @see #getFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes)
+         */
+        @NonNull
+        public Builder setFadeInVolumeShaperConfigForAudioAttributes(
+                @NonNull AudioAttributes audioAttributes,
+                @Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
+            Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
+            getFadeVolShaperConfigWrapperForAttr(audioAttributes)
+                    .setFadeInVolShaperConfig(fadeInVShaperConfig);
+            cleanupInactiveWrapperEntries(audioAttributes);
+            return this;
+        }
+
+        /**
+         * Set the duration used for fading out players of type
+         * {@link android.media.AudioAttributes}.
+         * <p>
+         * A Volume shaper configuration is generated with the provided duration and default
+         * volume curve definitions. This config is then used to fade out players with given usage.
+         * <p>
+         * <b>Note: </b>In order to clear previously set duration (example, if set through
+         * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
+         * {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
+         * {@code null}
+         *
+         * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade out
+         * duration will be set/updated/reset
+         * @param fadeOutDurationMillis positive duration in milliseconds or
+         * {@link #DURATION_NOT_SET}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the fade out duration is non-positive with the
+         * exception of {@link #DURATION_NOT_SET}
+         * @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
+         * @see #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
+         * VolumeShaper.Configuration)
+         */
+        @NonNull
+        public Builder setFadeOutDurationForAudioAttributes(
+                @NonNull AudioAttributes audioAttributes,
+                long fadeOutDurationMillis) {
+            Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
+            VolumeShaper.Configuration fadeOutVShaperConfig =
+                    createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
+            setFadeOutVolumeShaperConfigForAudioAttributes(audioAttributes, fadeOutVShaperConfig);
+            return this;
+        }
+
+        /**
+         * Set the duration used for fading in players of type
+         * {@link android.media.AudioAttributes}.
+         * <p>
+         * A Volume shaper configuration is generated with the provided duration and default
+         * volume curve definitions. This config is then used to fade in players with given usage.
+         * <p>
+         * <b>Note: </b>In order to clear previously set duration (example, if set through
+         * {@link #Builder(android.media.FadeManagerConfiguration)}), this method accepts
+         * {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
+         * {@code null}
+         *
+         * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade in
+         * duration will be set/updated/reset
+         * @param fadeInDurationMillis positive duration in milliseconds or
+         * {@link #DURATION_NOT_SET}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the fade in duration is non-positive with the
+         * exception of {@link #DURATION_NOT_SET}
+         * @see #getFadeInDurationForAudioAttributes(AudioAttributes)
+         * @see #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
+         * VolumeShaper.Configuration)
+         */
+        @NonNull
+        public Builder setFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes,
+                long fadeInDurationMillis) {
+            Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
+            VolumeShaper.Configuration fadeInVShaperConfig =
+                    createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
+            setFadeInVolumeShaperConfigForAudioAttributes(audioAttributes, fadeInVShaperConfig);
+            return this;
+        }
+
+        /**
+         * Set the list of {@link android.media.AudioAttributes.AttributeUsage} that can be faded
+         *
+         * <p>This is a positive list. Players with matching usage will be considered for fading.
+         * Usages that are not part of this list will not be faded
+         *
+         * <p>Passing an empty list as input clears the existing list. This can be used to
+         * reset the list when using a copy constructor
+         *
+         * <p><b>Warning:</b> When fade state is set to enabled, the builder expects at least one
+         * usage to be set/added. Failure to do so will result in an exception during
+         * {@link #build()}
+         *
+         * @param usages List of the {@link android.media.AudioAttributes.AttributeUsage}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the usages are invalid
+         * @throws NullPointerException if the usage list is {@code null}
+         * @see #getFadeableUsages()
+         */
+        @NonNull
+        public Builder setFadeableUsages(@NonNull List<Integer> usages) {
+            Objects.requireNonNull(usages, "List of usages cannot be null");
+            validateUsages(usages);
+            setFlag(IS_FADEABLE_USAGES_FIELD_SET);
+            mFadeableUsages.clear();
+            mFadeableUsages.addAll(convertIntegerListToIntArray(usages));
+            return this;
+        }
+
+        /**
+         * Add the {@link android.media.AudioAttributes.AttributeUsage} to the fadeable list
+         *
+         * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the usage is invalid
+         * @see #getFadeableUsages()
+         * @see #setFadeableUsages(List)
+         */
+        @NonNull
+        public Builder addFadeableUsage(@AudioAttributes.AttributeUsage int usage) {
+            validateUsage(usage);
+            setFlag(IS_FADEABLE_USAGES_FIELD_SET);
+            if (!mFadeableUsages.contains(usage)) {
+                mFadeableUsages.add(usage);
+            }
+            return this;
+        }
+
+        /**
+         * Remove the {@link android.media.AudioAttributes.AttributeUsage} from the fadeable list
+         * <p>
+         * Players of this usage type will not be faded.
+         *
+         * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the usage is invalid
+         * @see #getFadeableUsages()
+         * @see #setFadeableUsages(List)
+         */
+        @NonNull
+        public Builder clearFadeableUsage(@AudioAttributes.AttributeUsage int usage) {
+            validateUsage(usage);
+            setFlag(IS_FADEABLE_USAGES_FIELD_SET);
+            int index = mFadeableUsages.indexOf(usage);
+            if (index != INVALID_INDEX) {
+                mFadeableUsages.remove(index);
+            }
+            return this;
+        }
+
+        /**
+         * Set the list of {@link android.media.AudioAttributes.AttributeContentType} that can not
+         * be faded
+         *
+         * <p>This is a negative list. Players with matching content type of this list will not be
+         * faded. Content types that are not part of this list will be considered for fading.
+         *
+         * <p>Passing an empty list as input clears the existing list. This can be used to
+         * reset the list when using a copy constructor
+         *
+         * @param contentTypes list of {@link android.media.AudioAttributes.AttributeContentType}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the content types are invalid
+         * @throws NullPointerException if the content type list is {@code null}
+         * @see #getUnfadeableContentTypes()
+         */
+        @NonNull
+        public Builder setUnfadeableContentTypes(@NonNull List<Integer> contentTypes) {
+            Objects.requireNonNull(contentTypes, "List of content types cannot be null");
+            validateContentTypes(contentTypes);
+            setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
+            mUnfadeableContentTypes.clear();
+            mUnfadeableContentTypes.addAll(convertIntegerListToIntArray(contentTypes));
+            return this;
+        }
+
+        /**
+         * Add the {@link android.media.AudioAttributes.AttributeContentType} to unfadeable list
+         *
+         * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the content type is invalid
+         * @see #setUnfadeableContentTypes(List)
+         * @see #getUnfadeableContentTypes()
+         */
+        @NonNull
+        public Builder addUnfadeableContentType(
+                @AudioAttributes.AttributeContentType int contentType) {
+            validateContentType(contentType);
+            setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
+            if (!mUnfadeableContentTypes.contains(contentType)) {
+                mUnfadeableContentTypes.add(contentType);
+            }
+            return this;
+        }
+
+        /**
+         * Remove the {@link android.media.AudioAttributes.AttributeContentType} from the
+         * unfadeable list
+         *
+         * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the content type is invalid
+         * @see #setUnfadeableContentTypes(List)
+         * @see #getUnfadeableContentTypes()
+         */
+        @NonNull
+        public Builder clearUnfadeableContentType(
+                @AudioAttributes.AttributeContentType int contentType) {
+            validateContentType(contentType);
+            setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
+            int index = mUnfadeableContentTypes.indexOf(contentType);
+            if (index != INVALID_INDEX) {
+                mUnfadeableContentTypes.remove(index);
+            }
+            return this;
+        }
+
+        /**
+         * Set the uids that cannot be faded
+         *
+         * <p>This is a negative list. Players with matching uid of this list will not be faded.
+         * Uids that are not part of this list shall be considered for fading
+         *
+         * <p>Passing an empty list as input clears the existing list. This can be used to
+         * reset the list when using a copy constructor
+         *
+         * @param uids list of uids
+         * @return the same Builder instance
+         * @throws NullPointerException if the uid list is {@code null}
+         * @see #getUnfadeableUids()
+         */
+        @NonNull
+        public Builder setUnfadeableUids(@NonNull List<Integer> uids) {
+            Objects.requireNonNull(uids, "List of uids cannot be null");
+            mUnfadeableUids.clear();
+            mUnfadeableUids.addAll(convertIntegerListToIntArray(uids));
+            return this;
+        }
+
+        /**
+         * Add uid to unfadeable list
+         *
+         * @param uid client uid
+         * @return the same Builder instance
+         * @see #setUnfadeableUids(List)
+         * @see #getUnfadeableUids()
+         */
+        @NonNull
+        public Builder addUnfadeableUid(int uid) {
+            if (!mUnfadeableUids.contains(uid)) {
+                mUnfadeableUids.add(uid);
+            }
+            return this;
+        }
+
+        /**
+         * Remove the uid from unfadeable list
+         *
+         * @param uid client uid
+         * @return the same Builder instance
+         * @see #setUnfadeableUids(List)
+         * @see #getUnfadeableUids()
+         */
+        @NonNull
+        public Builder clearUnfadeableUid(int uid) {
+            int index = mUnfadeableUids.indexOf(uid);
+            if (index != INVALID_INDEX) {
+                mUnfadeableUids.remove(index);
+            }
+            return this;
+        }
+
+        /**
+         * Set the list of {@link android.media.AudioAttributes} that can not be faded
+         *
+         * <p>This is a negative list. Players with matching audio attributes of this list will not
+         * be faded. Audio attributes that are not part of this list shall be considered for fading.
+         *
+         * <p>Passing an empty list as input clears any existing list. This can be used to
+         * reset the list when using a copy constructor
+         *
+         * <p><b>Note:</b> Be cautious when adding generic audio attributes into this list as it can
+         * negatively impact fadeability decision if such an audio attribute and corresponding
+         * usage fall into opposing lists.
+         * For example:
+         * <pre class=prettyprint>
+         *    AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build() </pre>
+         * is a generic audio attribute for {@link android.media.AudioAttributes.USAGE_MEDIA}.
+         * It is an undefined behavior to have an
+         * {@link android.media.AudioAttributes.AttributeUsage} in the fadeable usage list and the
+         * corresponding generic {@link android.media.AudioAttributes} in the unfadeable list. Such
+         * cases will result in an exception during {@link #build()}
+         *
+         * @param attrs list of {@link android.media.AudioAttributes}
+         * @return the same Builder instance
+         * @throws NullPointerException if the audio attributes list is {@code null}
+         * @see #getUnfadeableAudioAttributes()
+         */
+        @NonNull
+        public Builder setUnfadeableAudioAttributes(@NonNull List<AudioAttributes> attrs) {
+            Objects.requireNonNull(attrs, "List of audio attributes cannot be null");
+            mUnfadeableAudioAttributes.clear();
+            mUnfadeableAudioAttributes.addAll(attrs);
+            return this;
+        }
+
+        /**
+         * Add the {@link android.media.AudioAttributes} to the unfadeable list
+         *
+         * @param audioAttributes the {@link android.media.AudioAttributes}
+         * @return the same Builder instance
+         * @throws NullPointerException if the audio attributes is {@code null}
+         * @see #setUnfadeableAudioAttributes(List)
+         * @see #getUnfadeableAudioAttributes()
+         */
+        @NonNull
+        public Builder addUnfadeableAudioAttributes(@NonNull AudioAttributes audioAttributes) {
+            Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
+            if (!mUnfadeableAudioAttributes.contains(audioAttributes)) {
+                mUnfadeableAudioAttributes.add(audioAttributes);
+            }
+            return this;
+        }
+
+        /**
+         * Remove the {@link android.media.AudioAttributes} from the unfadeable list.
+         *
+         * @param audioAttributes the {@link android.media.AudioAttributes}
+         * @return the same Builder instance
+         * @throws NullPointerException if the audio attributes is {@code null}
+         * @see #getUnfadeableAudioAttributes()
+         */
+        @NonNull
+        public Builder clearUnfadeableAudioAttributes(@NonNull AudioAttributes audioAttributes) {
+            Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
+            if (mUnfadeableAudioAttributes.contains(audioAttributes)) {
+                mUnfadeableAudioAttributes.remove(audioAttributes);
+            }
+            return this;
+        }
+
+        /**
+         * Set the delay after which the offending faded out player will be faded in.
+         *
+         * <p>This is the amount of time between the app being notified of the focus loss (when its
+         * muted by the fade out), and the time fade in (to unmute) starts
+         *
+         * @param delayMillis delay in milliseconds
+         * @return the same Builder instance
+         * @throws IllegalArgumentException if the delay is negative
+         * @see #getFadeInDelayForOffenders()
+         */
+        @NonNull
+        public Builder setFadeInDelayForOffenders(long delayMillis) {
+            Preconditions.checkArgument(delayMillis >= 0, "Delay cannot be negative");
+            mFadeInDelayForOffendersMillis = delayMillis;
+            return this;
+        }
+
+        /**
+         * Builds the {@link FadeManagerConfiguration} with all of the fade configurations that
+         * have been set.
+         *
+         * @return a new {@link FadeManagerConfiguration} object
+         */
+        @NonNull
+        public FadeManagerConfiguration build() {
+            if (!checkNotSet(IS_BUILDER_USED_FIELD_SET)) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+
+            setFlag(IS_BUILDER_USED_FIELD_SET);
+
+            if (checkNotSet(IS_FADEABLE_USAGES_FIELD_SET)) {
+                mFadeableUsages = DEFAULT_FADEABLE_USAGES;
+                setVolShaperConfigsForUsages(mFadeableUsages);
+            }
+
+            if (checkNotSet(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET)) {
+                mUnfadeableContentTypes = DEFAULT_UNFADEABLE_CONTENT_TYPES;
+            }
+
+            validateFadeConfigurations();
+
+            return new FadeManagerConfiguration(mFadeState, mFadeOutDurationMillis,
+                    mFadeInDurationMillis, mFadeInDelayForOffendersMillis, mUsageToFadeWrapperMap,
+                    mAttrToFadeWrapperMap, mFadeableUsages, mUnfadeableContentTypes,
+                    mUnfadeablePlayerTypes, mUnfadeableUids, mUnfadeableAudioAttributes);
+        }
+
+        private void setFlag(long flag) {
+            mBuilderFieldsSet |= flag;
+        }
+
+        private boolean checkNotSet(long flag) {
+            return (mBuilderFieldsSet & flag) == 0;
+        }
+
+        private FadeVolumeShaperConfigsWrapper getFadeVolShaperConfigWrapperForUsage(int usage) {
+            if (!mUsageToFadeWrapperMap.contains(usage)) {
+                mUsageToFadeWrapperMap.put(usage, new FadeVolumeShaperConfigsWrapper());
+            }
+            return mUsageToFadeWrapperMap.get(usage);
+        }
+
+        private FadeVolumeShaperConfigsWrapper getFadeVolShaperConfigWrapperForAttr(
+                AudioAttributes attr) {
+            // if no entry, create a new one for setting/clearing
+            if (!mAttrToFadeWrapperMap.containsKey(attr)) {
+                mAttrToFadeWrapperMap.put(attr, new FadeVolumeShaperConfigsWrapper());
+            }
+            return mAttrToFadeWrapperMap.get(attr);
+        }
+
+        private VolumeShaper.Configuration createVolShaperConfigForDuration(long duration,
+                boolean isFadeIn) {
+            // used to reset the volume shaper config setting
+            if (duration == DURATION_NOT_SET) {
+                return null;
+            }
+
+            VolumeShaper.Configuration.Builder builder = new VolumeShaper.Configuration.Builder()
+                    .setId(VOLUME_SHAPER_SYSTEM_FADE_ID)
+                    .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+                    .setDuration(duration);
+
+            if (isFadeIn) {
+                builder.setCurve(/* times= */ new float[]{0.f, 0.50f, 1.0f},
+                        /* volumes= */ new float[]{0.f, 0.30f, 1.0f});
+            } else {
+                builder.setCurve(/* times= */ new float[]{0.f, 0.25f, 1.0f},
+                        /* volumes= */ new float[]{1.f, 0.65f, 0.0f});
+            }
+
+            return builder.build();
+        }
+
+        private void cleanupInactiveWrapperEntries(int usage) {
+            FadeVolumeShaperConfigsWrapper fmcw = mUsageToFadeWrapperMap.get(usage);
+            // cleanup map entry if FadeVolumeShaperConfigWrapper is inactive
+            if (fmcw != null && fmcw.isInactive()) {
+                mUsageToFadeWrapperMap.remove(usage);
+            }
+        }
+
+        private void cleanupInactiveWrapperEntries(AudioAttributes attr) {
+            FadeVolumeShaperConfigsWrapper fmcw = mAttrToFadeWrapperMap.get(attr);
+            // cleanup map entry if FadeVolumeShaperConfigWrapper is inactive
+            if (fmcw != null && fmcw.isInactive()) {
+                mAttrToFadeWrapperMap.remove(attr);
+            }
+        }
+
+        private void setVolShaperConfigsForUsages(IntArray usages) {
+            // set default volume shaper configs for fadeable usages
+            for (int index = 0; index < usages.size(); index++) {
+                setMissingVolShaperConfigsForWrapper(
+                        getFadeVolShaperConfigWrapperForUsage(usages.get(index)));
+            }
+        }
+
+        private void setMissingVolShaperConfigsForWrapper(FadeVolumeShaperConfigsWrapper wrapper) {
+            if (!wrapper.isFadeOutConfigActive()) {
+                wrapper.setFadeOutVolShaperConfig(createVolShaperConfigForDuration(
+                        mFadeOutDurationMillis, /* isFadeIn= */ false));
+            }
+            if (!wrapper.isFadeInConfigActive()) {
+                wrapper.setFadeInVolShaperConfig(createVolShaperConfigForDuration(
+                        mFadeInDurationMillis, /* isFadeIn= */ true));
+            }
+        }
+
+        private void validateFadeState(int state) {
+            switch(state) {
+                case FADE_STATE_DISABLED:
+                case FADE_STATE_ENABLED_DEFAULT:
+                case FADE_STATE_ENABLED_AUTO:
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown fade state: " + state);
+            }
+        }
+
+        private void validateUsages(List<Integer> usages) {
+            for (int index = 0; index < usages.size(); index++) {
+                validateUsage(usages.get(index));
+            }
+        }
+
+        private void validateContentTypes(List<Integer> contentTypes) {
+            for (int index = 0; index < contentTypes.size(); index++) {
+                validateContentType(contentTypes.get(index));
+            }
+        }
+
+        private void validateContentType(int contentType) {
+            Preconditions.checkArgument(AudioAttributes.isSdkContentType(contentType),
+                    "Invalid content type: ", contentType);
+        }
+
+        private void validateFadeConfigurations() {
+            validateFadeableUsages();
+            validateFadeVolumeShaperConfigsWrappers();
+            validateUnfadeableAudioAttributes();
+        }
+
+        /** Ensure fadeable usage list meets config requirements */
+        private void validateFadeableUsages() {
+            // ensure at least one fadeable usage
+            Preconditions.checkArgumentPositive(mFadeableUsages.size(),
+                    "Fadeable usage list cannot be empty when state set to enabled");
+            // ensure all fadeable usages have volume shaper configs - both fade in and out
+            for (int index = 0; index < mFadeableUsages.size(); index++) {
+                setMissingVolShaperConfigsForWrapper(
+                        getFadeVolShaperConfigWrapperForUsage(mFadeableUsages.get(index)));
+            }
+        }
+
+        /** Ensure Fade volume shaper config wrappers meet requirements */
+        private void validateFadeVolumeShaperConfigsWrappers() {
+            // ensure both fade in & out volume shaper configs are defined for all wrappers
+            // for usages -
+            for (int index = 0; index < mUsageToFadeWrapperMap.size(); index++) {
+                setMissingVolShaperConfigsForWrapper(
+                        getFadeVolShaperConfigWrapperForUsage(mUsageToFadeWrapperMap.keyAt(index)));
+            }
+
+            // for additional audio attributes -
+            for (int index = 0; index < mAttrToFadeWrapperMap.size(); index++) {
+                setMissingVolShaperConfigsForWrapper(
+                        getFadeVolShaperConfigWrapperForAttr(mAttrToFadeWrapperMap.keyAt(index)));
+            }
+        }
+
+        /** Ensure Unfadeable attributes meet configuration requirements */
+        private void validateUnfadeableAudioAttributes() {
+            // ensure no generic AudioAttributes in unfadeable list with matching usage in fadeable
+            // list. failure results in an undefined behavior as the audio attributes
+            // shall be both fadeable (because of the usage) and unfadeable at the same time.
+            for (int index = 0; index < mUnfadeableAudioAttributes.size(); index++) {
+                AudioAttributes targetAttr = mUnfadeableAudioAttributes.get(index);
+                int usage = targetAttr.getSystemUsage();
+                boolean isFadeableUsage = mFadeableUsages.contains(usage);
+                // cannot have a generic audio attribute that also is a fadeable usage
+                Preconditions.checkArgument(
+                        !isFadeableUsage || (isFadeableUsage && !isGeneric(targetAttr)),
+                        "Unfadeable audio attributes cannot be generic of the fadeable usage");
+            }
+        }
+
+        private static boolean isGeneric(AudioAttributes attr) {
+            return (attr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN
+                    && attr.getFlags() == 0x0
+                    && attr.getBundle() == null
+                    && attr.getTags().isEmpty());
+        }
+    }
+
+    private static final class FadeVolumeShaperConfigsWrapper implements Parcelable {
+        // null volume shaper config refers to either init state or if its cleared/reset
+        private @Nullable VolumeShaper.Configuration mFadeOutVolShaperConfig;
+        private @Nullable VolumeShaper.Configuration mFadeInVolShaperConfig;
+
+        FadeVolumeShaperConfigsWrapper() {}
+
+        public void setFadeOutVolShaperConfig(@Nullable VolumeShaper.Configuration fadeOutConfig) {
+            mFadeOutVolShaperConfig = fadeOutConfig;
+        }
+
+        public void setFadeInVolShaperConfig(@Nullable VolumeShaper.Configuration fadeInConfig) {
+            mFadeInVolShaperConfig = fadeInConfig;
+        }
+
+        /**
+         * Query fade out volume shaper config
+         *
+         * @return configured fade out volume shaper config or {@code null} when initialized/reset
+         */
+        @Nullable
+        public VolumeShaper.Configuration getFadeOutVolShaperConfig() {
+            return mFadeOutVolShaperConfig;
+        }
+
+        /**
+         * Query fade in volume shaper config
+         *
+         * @return configured fade in volume shaper config or {@code null} when initialized/reset
+         */
+        @Nullable
+        public VolumeShaper.Configuration getFadeInVolShaperConfig() {
+            return mFadeInVolShaperConfig;
+        }
+
+        /**
+         * Wrapper is inactive if both fade out and in configs are cleared.
+         *
+         * @return {@code true} if configs are cleared. {@code false} if either of the configs is
+         * set
+         */
+        public boolean isInactive() {
+            return !isFadeOutConfigActive() && !isFadeInConfigActive();
+        }
+
+        boolean isFadeOutConfigActive() {
+            return mFadeOutVolShaperConfig != null;
+        }
+
+        boolean isFadeInConfigActive() {
+            return mFadeInVolShaperConfig != null;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof FadeVolumeShaperConfigsWrapper)) {
+                return false;
+            }
+
+            FadeVolumeShaperConfigsWrapper rhs = (FadeVolumeShaperConfigsWrapper) o;
+
+            if (mFadeInVolShaperConfig == null && rhs.mFadeInVolShaperConfig == null
+                    && mFadeOutVolShaperConfig == null && rhs.mFadeOutVolShaperConfig == null) {
+                return true;
+            }
+
+            boolean isEqual;
+            if (mFadeOutVolShaperConfig != null) {
+                isEqual = mFadeOutVolShaperConfig.equals(rhs.mFadeOutVolShaperConfig);
+            } else if (rhs.mFadeOutVolShaperConfig != null) {
+                return false;
+            } else {
+                isEqual = true;
+            }
+
+            if (mFadeInVolShaperConfig != null) {
+                isEqual = isEqual && mFadeInVolShaperConfig.equals(rhs.mFadeInVolShaperConfig);
+            } else if (rhs.mFadeInVolShaperConfig != null) {
+                return false;
+            }
+
+            return isEqual;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mFadeOutVolShaperConfig, mFadeInVolShaperConfig);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            mFadeOutVolShaperConfig.writeToParcel(dest, flags);
+            mFadeInVolShaperConfig.writeToParcel(dest, flags);
+        }
+
+        /**
+         * Creates fade volume shaper config wrapper from parcel
+         *
+         * @hide
+         */
+        @VisibleForTesting()
+        FadeVolumeShaperConfigsWrapper(Parcel in) {
+            mFadeOutVolShaperConfig = VolumeShaper.Configuration.CREATOR.createFromParcel(in);
+            mFadeInVolShaperConfig = VolumeShaper.Configuration.CREATOR.createFromParcel(in);
+        }
+
+        @NonNull
+        public static final Creator<FadeVolumeShaperConfigsWrapper> CREATOR = new Creator<>() {
+            @Override
+            @NonNull
+            public FadeVolumeShaperConfigsWrapper createFromParcel(@NonNull Parcel in) {
+                return new FadeVolumeShaperConfigsWrapper(in);
+            }
+
+            @Override
+            @NonNull
+            public FadeVolumeShaperConfigsWrapper[] newArray(int size) {
+                return new FadeVolumeShaperConfigsWrapper[size];
+            }
+        };
+    }
+}
+
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5c8758a..a52f0b0 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -338,10 +338,20 @@
     oneway void setCsdAsAFeatureEnabled(boolean csdToggleValue);
 
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
-    oneway void setBluetoothAudioDeviceCategory(in String address, boolean isBle, int deviceType);
+    oneway void setBluetoothAudioDeviceCategory_legacy(in String address, boolean isBle,
+            int deviceCategory);
 
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
-    int getBluetoothAudioDeviceCategory(in String address, boolean isBle);
+    int getBluetoothAudioDeviceCategory_legacy(in String address, boolean isBle);
+
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    boolean setBluetoothAudioDeviceCategory(in String address, int deviceCategory);
+
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    int getBluetoothAudioDeviceCategory(in String address);
+
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    boolean isBluetoothAudioDeviceCategoryFixed(in String address);
 
     int setHdmiSystemAudioSupported(boolean on);
 
diff --git a/media/java/android/media/flags/fade_manager_configuration.aconfig b/media/java/android/media/flags/fade_manager_configuration.aconfig
new file mode 100644
index 0000000..100e2235
--- /dev/null
+++ b/media/java/android/media/flags/fade_manager_configuration.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.media.flags"
+
+flag {
+    namespace: "media_solutions"
+    name: "enable_fade_manager_configuration"
+    description: "Enable Fade Manager Configuration support to determine fade properties"
+    bug: "307354764"
+}
\ No newline at end of file
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 4624dfe..3dc2a0a 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -17,6 +17,7 @@
         "guava-android-testlib",
         "hamcrest-library",
         "platform-test-annotations",
+        "truth",
     ],
     platform_apis: true,
     certificate: "platform",
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
index 94df40d..e9a0d3e 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioManagerTest.java
@@ -229,7 +229,7 @@
 
     @Test
     public void testSetGetVolumePerAttributes() {
-        for (int usage : AudioAttributes.SDK_USAGES) {
+        for (int usage : AudioAttributes.getSdkUsages()) {
             if (usage == AudioAttributes.USAGE_UNKNOWN) {
                 continue;
             }
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
index 266faae..18e8608 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioProductStrategyTest.java
@@ -169,7 +169,7 @@
         assertNotNull(audioProductStrategies);
         assertTrue(audioProductStrategies.size() > 0);
 
-        for (int usage : AudioAttributes.SDK_USAGES) {
+        for (int usage : AudioAttributes.getSdkUsages()) {
             AudioAttributes aaForUsage = new AudioAttributes.Builder().setUsage(usage).build();
 
             int streamTypeFromUsage =
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
new file mode 100644
index 0000000..fb6bd48
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.audiopolicytest;
+
+import static com.android.media.flags.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
+
+import static org.junit.Assert.assertThrows;
+
+import android.media.AudioAttributes;
+import android.media.AudioPlaybackConfiguration;
+import android.media.FadeManagerConfiguration;
+import android.media.VolumeShaper;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
+public final class FadeManagerConfigurationUnitTest {
+    private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+    private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
+    private static final long TEST_FADE_OUT_DURATION_MS = 1_500;
+    private static final long TEST_FADE_IN_DURATION_MS = 750;
+    private static final int TEST_INVALID_USAGE = -10;
+    private static final int TEST_INVALID_CONTENT_TYPE = 100;
+    private static final int TEST_INVALID_FADE_STATE = 100;
+    private static final long TEST_INVALID_DURATION = -10;
+    private static final int TEST_UID_1 = 1010001;
+    private static final int TEST_UID_2 = 1000;
+    private static final int TEST_PARCEL_FLAGS = 0;
+    private static final AudioAttributes TEST_MEDIA_AUDIO_ATTRIBUTE =
+            createAudioAttributesForUsage(AudioAttributes.USAGE_MEDIA);
+    private static final AudioAttributes TEST_GAME_AUDIO_ATTRIBUTE =
+            createAudioAttributesForUsage(AudioAttributes.USAGE_GAME);
+    private static final AudioAttributes TEST_NAVIGATION_AUDIO_ATTRIBUTE =
+            new AudioAttributes.Builder().setUsage(
+                    AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                    .build();
+    private static final AudioAttributes TEST_ASSISTANT_AUDIO_ATTRIBUTE =
+            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ASSISTANT)
+                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build();
+    private static final List<Integer> TEST_FADEABLE_USAGES = Arrays.asList(
+            AudioAttributes.USAGE_MEDIA,
+            AudioAttributes.USAGE_GAME
+    );
+    private static final List<Integer> TEST_UNFADEABLE_CONTENT_TYPES = Arrays.asList(
+            AudioAttributes.CONTENT_TYPE_SPEECH
+    );
+
+    private static final List<Integer> TEST_UNFADEABLE_PLAYER_TYPES = Arrays.asList(
+            AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
+            AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
+    );
+    private static final VolumeShaper.Configuration TEST_DEFAULT_FADE_OUT_VOLUME_SHAPER_CONFIG =
+            new VolumeShaper.Configuration.Builder()
+                    .setId(FadeManagerConfiguration.VOLUME_SHAPER_SYSTEM_FADE_ID)
+                    .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f},
+                            /* volumes= */new float[]{1.f, 0.65f, 0.0f})
+                    .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+                    .setDuration(DEFAULT_FADE_OUT_DURATION_MS)
+                    .build();
+    private static final VolumeShaper.Configuration TEST_DEFAULT_FADE_IN_VOLUME_SHAPER_CONFIG =
+            new VolumeShaper.Configuration.Builder()
+                    .setId(FadeManagerConfiguration.VOLUME_SHAPER_SYSTEM_FADE_ID)
+                    .setCurve(/* times= */new float[]{0.f, 0.50f, 1.0f},
+                            /* volumes= */new float[]{0.f, 0.30f, 1.0f})
+                    .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+                    .setDuration(DEFAULT_FADE_IN_DURATION_MS)
+                    .build();
+    private static final VolumeShaper.Configuration TEST_FADE_OUT_VOLUME_SHAPER_CONFIG =
+            new VolumeShaper.Configuration.Builder()
+                    .setId(FadeManagerConfiguration.VOLUME_SHAPER_SYSTEM_FADE_ID)
+                    .setCurve(/* times= */new float[]{0.f, 0.25f, 1.0f},
+                            /* volumes= */new float[]{1.f, 0.65f, 0.0f})
+                    .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+                    .setDuration(TEST_FADE_OUT_DURATION_MS)
+                    .build();
+    private static final VolumeShaper.Configuration TEST_FADE_IN_VOLUME_SHAPER_CONFIG =
+            new VolumeShaper.Configuration.Builder()
+                    .setId(FadeManagerConfiguration.VOLUME_SHAPER_SYSTEM_FADE_ID)
+                    .setCurve(/* times= */new float[]{0.f, 0.50f, 1.0f},
+                            /* volumes= */new float[]{0.f, 0.30f, 1.0f})
+                    .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+                    .setDuration(TEST_FADE_IN_DURATION_MS)
+                    .build();
+
+    private FadeManagerConfiguration mFmc;
+
+    @Rule
+    public final Expect expect = Expect.create();
+
+    @Before
+    public void setUp() {
+        mFmc = new FadeManagerConfiguration.Builder().build();
+    }
+
+
+    @Test
+    public void build() {
+        expect.withMessage("Fade state for default builder")
+                .that(mFmc.getFadeState())
+                .isEqualTo(FadeManagerConfiguration.FADE_STATE_ENABLED_DEFAULT);
+        expect.withMessage("Fadeable usages for default builder")
+                .that(mFmc.getFadeableUsages())
+                .containsExactlyElementsIn(TEST_FADEABLE_USAGES);
+        expect.withMessage("Unfadeable content types usages for default builder")
+                .that(mFmc.getUnfadeableContentTypes())
+                .containsExactlyElementsIn(TEST_UNFADEABLE_CONTENT_TYPES);
+        expect.withMessage("Unfadeable player types for default builder")
+                .that(mFmc.getUnfadeablePlayerTypes())
+                .containsExactlyElementsIn(TEST_UNFADEABLE_PLAYER_TYPES);
+        expect.withMessage("Unfadeable uids for default builder")
+                .that(mFmc.getUnfadeableUids()).isEmpty();
+        expect.withMessage("Unfadeable audio attributes for default builder")
+                .that(mFmc.getUnfadeableAudioAttributes()).isEmpty();
+        expect.withMessage("Fade out volume shaper config for media usage")
+                .that(mFmc.getFadeOutVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+                .isEqualTo(TEST_DEFAULT_FADE_OUT_VOLUME_SHAPER_CONFIG);
+        expect.withMessage("Fade out duration for game usage")
+                .that(mFmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_GAME))
+                .isEqualTo(DEFAULT_FADE_OUT_DURATION_MS);
+        expect.withMessage("Fade in volume shaper config for media uasge")
+                .that(mFmc.getFadeInVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+                .isEqualTo(TEST_DEFAULT_FADE_IN_VOLUME_SHAPER_CONFIG);
+        expect.withMessage("Fade in duration for game audio usage")
+                .that(mFmc.getFadeInDurationForUsage(AudioAttributes.USAGE_GAME))
+                .isEqualTo(DEFAULT_FADE_IN_DURATION_MS);
+    }
+
+    @Test
+    public void build_withFadeDurations_succeeds() {
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration
+                .Builder(TEST_FADE_OUT_DURATION_MS, TEST_FADE_IN_DURATION_MS).build();
+
+        expect.withMessage("Fade state for builder with duration").that(fmc.getFadeState())
+                .isEqualTo(FadeManagerConfiguration.FADE_STATE_ENABLED_DEFAULT);
+        expect.withMessage("Fadeable usages for builder with duration")
+                .that(fmc.getFadeableUsages())
+                .containsExactlyElementsIn(TEST_FADEABLE_USAGES);
+        expect.withMessage("Unfadeable content types usages for builder with duration")
+                .that(fmc.getUnfadeableContentTypes())
+                .containsExactlyElementsIn(TEST_UNFADEABLE_CONTENT_TYPES);
+        expect.withMessage("Unfadeable player types for builder with duration")
+                .that(fmc.getUnfadeablePlayerTypes())
+                .containsExactlyElementsIn(TEST_UNFADEABLE_PLAYER_TYPES);
+        expect.withMessage("Unfadeable uids for builder with duration")
+                .that(fmc.getUnfadeableUids()).isEmpty();
+        expect.withMessage("Unfadeable audio attributes for builder with duration")
+                .that(fmc.getUnfadeableAudioAttributes()).isEmpty();
+        expect.withMessage("Fade out volume shaper config for media usage")
+                .that(fmc.getFadeOutVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+                .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+        expect.withMessage("Fade out duration for game usage")
+                .that(fmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_GAME))
+                .isEqualTo(TEST_FADE_OUT_DURATION_MS);
+        expect.withMessage("Fade in volume shaper config for media audio attributes")
+                .that(fmc.getFadeInVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+                .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+        expect.withMessage("Fade in duration for game audio attributes")
+                .that(fmc.getFadeInDurationForUsage(AudioAttributes.USAGE_GAME))
+                .isEqualTo(TEST_FADE_IN_DURATION_MS);
+
+    }
+
+    @Test
+    public void build_withFadeManagerConfiguration_succeeds() {
+        FadeManagerConfiguration fmcObj = new FadeManagerConfiguration
+                .Builder(TEST_FADE_OUT_DURATION_MS, TEST_FADE_IN_DURATION_MS).build();
+
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration
+                .Builder(fmcObj).build();
+
+        expect.withMessage("Fade state for copy builder").that(fmc.getFadeState())
+                .isEqualTo(fmcObj.getFadeState());
+        expect.withMessage("Fadeable usages for copy builder")
+                .that(fmc.getFadeableUsages())
+                .containsExactlyElementsIn(fmcObj.getFadeableUsages());
+        expect.withMessage("Unfadeable content types usages for copy builder")
+                .that(fmc.getUnfadeableContentTypes())
+                .containsExactlyElementsIn(fmcObj.getUnfadeableContentTypes());
+        expect.withMessage("Unfadeable player types for copy builder")
+                .that(fmc.getUnfadeablePlayerTypes())
+                .containsExactlyElementsIn(fmcObj.getUnfadeablePlayerTypes());
+        expect.withMessage("Unfadeable uids for copy builder")
+                .that(fmc.getUnfadeableUids()).isEqualTo(fmcObj.getUnfadeableUids());
+        expect.withMessage("Unfadeable audio attributes for copy builder")
+                .that(fmc.getUnfadeableAudioAttributes())
+                .isEqualTo(fmcObj.getUnfadeableAudioAttributes());
+        expect.withMessage("Fade out volume shaper config for media usage")
+                .that(fmc.getFadeOutVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+                .isEqualTo(fmcObj.getFadeOutVolumeShaperConfigForUsage(
+                        AudioAttributes.USAGE_MEDIA));
+        expect.withMessage("Fade out volume shaper config for game usage")
+                .that(fmc.getFadeOutVolumeShaperConfigForUsage(AudioAttributes.USAGE_GAME))
+                .isEqualTo(fmcObj.getFadeOutVolumeShaperConfigForUsage(
+                        AudioAttributes.USAGE_GAME));
+        expect.withMessage("Fade in volume shaper config for media usage")
+                .that(fmc.getFadeInVolumeShaperConfigForUsage(AudioAttributes.USAGE_MEDIA))
+                .isEqualTo(fmcObj.getFadeInVolumeShaperConfigForUsage(
+                        AudioAttributes.USAGE_MEDIA));
+        expect.withMessage("Fade in volume shaper config for game usage")
+                .that(fmc.getFadeInVolumeShaperConfigForUsage(AudioAttributes.USAGE_GAME))
+                .isEqualTo(fmcObj.getFadeInVolumeShaperConfigForUsage(
+                        AudioAttributes.USAGE_GAME));
+        expect.withMessage("Fade out volume shaper config for media audio attributes")
+                .that(fmc.getFadeOutVolumeShaperConfigForAudioAttributes(
+                        TEST_MEDIA_AUDIO_ATTRIBUTE))
+                .isEqualTo(fmcObj.getFadeOutVolumeShaperConfigForAudioAttributes(
+                        TEST_MEDIA_AUDIO_ATTRIBUTE));
+        expect.withMessage("Fade out duration for game audio attributes")
+                .that(fmc.getFadeOutDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE))
+                .isEqualTo(fmcObj.getFadeOutDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE));
+        expect.withMessage("Fade in volume shaper config for media audio attributes")
+                .that(fmc.getFadeInVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE))
+                .isEqualTo(fmcObj.getFadeInVolumeShaperConfigForAudioAttributes(
+                        TEST_MEDIA_AUDIO_ATTRIBUTE));
+        expect.withMessage("Fade in duration for game audio attributes")
+                .that(fmc.getFadeInDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE))
+                .isEqualTo(fmcObj.getFadeInDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE));
+    }
+
+    @Test
+    public void testSetFadeState_toDisable() {
+        final int fadeState = FadeManagerConfiguration.FADE_STATE_DISABLED;
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setFadeState(fadeState).build();
+
+        expect.withMessage("Fade state when disabled").that(fmc.getFadeState())
+                .isEqualTo(fadeState);
+    }
+
+    @Test
+    public void testSetFadeState_toEnableAuto() {
+        final int fadeStateAuto = FadeManagerConfiguration.FADE_STATE_ENABLED_AUTO;
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setFadeState(fadeStateAuto).build();
+
+        expect.withMessage("Fade state when enabled for audio").that(fmc.getFadeState())
+                .isEqualTo(fadeStateAuto);
+    }
+
+    @Test
+    public void testSetFadeState_toInvalid_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                new FadeManagerConfiguration.Builder()
+                        .setFadeState(TEST_INVALID_FADE_STATE).build()
+        );
+
+        expect.withMessage("Invalid fade state exception").that(thrown)
+                .hasMessageThat().contains("Unknown fade state");
+    }
+
+    @Test
+    public void testSetFadeVolShaperConfig() {
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setFadeOutVolumeShaperConfigForAudioAttributes(TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForAudioAttributes(TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build();
+
+        expect.withMessage("Fade out volume shaper config set for assistant audio attributes")
+                .that(fmc.getFadeOutVolumeShaperConfigForAudioAttributes(
+                        TEST_ASSISTANT_AUDIO_ATTRIBUTE))
+                .isEqualTo(TEST_FADE_OUT_VOLUME_SHAPER_CONFIG);
+        expect.withMessage("Fade in volume shaper config set for assistant audio attributes")
+                .that(fmc.getFadeInVolumeShaperConfigForAudioAttributes(
+                        TEST_ASSISTANT_AUDIO_ATTRIBUTE))
+                .isEqualTo(TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+    }
+
+    @Test
+    public void testSetFadeOutVolShaperConfig_withNullAudioAttributes_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new FadeManagerConfiguration.Builder()
+                        .setFadeOutVolumeShaperConfigForAudioAttributes(/* audioAttributes= */ null,
+                                TEST_FADE_OUT_VOLUME_SHAPER_CONFIG).build()
+        );
+
+        expect.withMessage("Null audio attributes for fade out exception")
+                .that(thrown).hasMessageThat().contains("cannot be null");
+    }
+
+    @Test
+    public void testSetFadeVolShaperConfig_withNullVolumeShaper_getsNull() {
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder(mFmc)
+                .setFadeOutVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
+                        /* VolumeShaper.Configuration= */ null)
+                .setFadeInVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
+                        /* VolumeShaper.Configuration= */ null)
+                .clearFadeableUsage(AudioAttributes.USAGE_MEDIA).build();
+
+        expect.withMessage("Fade out volume shaper config set with null value")
+                .that(fmc.getFadeOutVolumeShaperConfigForAudioAttributes(
+                        TEST_MEDIA_AUDIO_ATTRIBUTE)).isNull();
+    }
+
+    @Test
+    public void testSetFadeInVolShaperConfig_withNullAudioAttributes_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new FadeManagerConfiguration.Builder()
+                        .setFadeInVolumeShaperConfigForAudioAttributes(/* audioAttributes= */ null,
+                                TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build()
+        );
+
+        expect.withMessage("Null audio attributes for fade in exception")
+                .that(thrown).hasMessageThat().contains("cannot be null");
+    }
+
+    @Test
+    public void testSetFadeDuration() {
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setFadeOutDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE,
+                        TEST_FADE_OUT_DURATION_MS)
+                .setFadeInDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE,
+                        TEST_FADE_IN_DURATION_MS).build();
+
+        expect.withMessage("Fade out duration set for audio attributes")
+                .that(fmc.getFadeOutDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE))
+                .isEqualTo(TEST_FADE_OUT_DURATION_MS);
+        expect.withMessage("Fade in duration set for audio attributes")
+                .that(fmc.getFadeInDurationForAudioAttributes(TEST_GAME_AUDIO_ATTRIBUTE))
+                .isEqualTo(TEST_FADE_IN_DURATION_MS);
+    }
+
+    @Test
+    public void testSetFadeOutDuration_withNullAudioAttributes_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new FadeManagerConfiguration.Builder().setFadeOutDurationForAudioAttributes(
+                        /* audioAttributes= */ null, TEST_FADE_OUT_DURATION_MS).build()
+        );
+
+        expect.withMessage("Null audio attributes for fade out duration exception").that(thrown)
+                .hasMessageThat().contains("cannot be null");
+    }
+
+    @Test
+    public void testSetFadeOutDuration_withInvalidDuration_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                new FadeManagerConfiguration.Builder().setFadeOutDurationForAudioAttributes(
+                        TEST_NAVIGATION_AUDIO_ATTRIBUTE, TEST_INVALID_DURATION).build()
+        );
+
+        expect.withMessage("Invalid duration for fade out exception").that(thrown)
+                .hasMessageThat().contains("not positive");
+    }
+
+    @Test
+    public void testSetFadeInDuration_withNullAudioAttributes_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new FadeManagerConfiguration.Builder().setFadeInDurationForAudioAttributes(
+                        /* audioAttributes= */ null, TEST_FADE_IN_DURATION_MS).build()
+        );
+
+        expect.withMessage("Null audio attributes for fade in duration exception").that(thrown)
+                .hasMessageThat().contains("cannot be null");
+    }
+
+    @Test
+    public void testSetFadeInDuration_withInvalidDuration_fails() {
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                new FadeManagerConfiguration.Builder().setFadeInDurationForAudioAttributes(
+                        TEST_NAVIGATION_AUDIO_ATTRIBUTE, TEST_INVALID_DURATION).build()
+        );
+
+        expect.withMessage("Invalid duration for fade in exception").that(thrown)
+                .hasMessageThat().contains("not positive");
+    }
+
+    @Test
+    public void testSetFadeableUsages() {
+        final List<Integer> fadeableUsages = List.of(
+                AudioAttributes.USAGE_VOICE_COMMUNICATION,
+                AudioAttributes.USAGE_ALARM,
+                AudioAttributes.USAGE_ASSISTANT
+                );
+        AudioAttributes aaForVoiceComm = createAudioAttributesForUsage(
+                AudioAttributes.USAGE_VOICE_COMMUNICATION);
+        AudioAttributes aaForAlarm = createAudioAttributesForUsage(AudioAttributes.USAGE_ALARM);
+        AudioAttributes aaForAssistant = createAudioAttributesForUsage(
+                AudioAttributes.USAGE_ASSISTANT);
+
+
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setFadeableUsages(fadeableUsages)
+                .setFadeOutVolumeShaperConfigForAudioAttributes(aaForVoiceComm,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForAudioAttributes(aaForVoiceComm,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+                .setFadeOutVolumeShaperConfigForAudioAttributes(aaForAlarm,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForAudioAttributes(aaForAlarm,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+                .setFadeOutVolumeShaperConfigForAudioAttributes(aaForAssistant,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForAudioAttributes(aaForAssistant,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build();
+
+        expect.withMessage("Fadeable usages")
+                .that(fmc.getFadeableUsages()).isEqualTo(fadeableUsages);
+    }
+
+    @Test
+    public void testSetFadeableUsages_withInvalidUsage_fails() {
+        final List<Integer> fadeableUsages = List.of(
+                AudioAttributes.USAGE_VOICE_COMMUNICATION,
+                TEST_INVALID_USAGE,
+                AudioAttributes.USAGE_ANNOUNCEMENT
+        );
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                new FadeManagerConfiguration.Builder().setFadeableUsages(fadeableUsages).build()
+        );
+
+        expect.withMessage("Fadeable usages set to invalid usage").that(thrown).hasMessageThat()
+                .contains("Invalid usage");
+    }
+
+    @Test
+    public void testSetFadeableUsages_withNullUsages_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new FadeManagerConfiguration.Builder().setFadeableUsages(/* usages= */ null)
+                        .build()
+        );
+
+        expect.withMessage("Fadeable usages set to null list").that(thrown).hasMessageThat()
+                .contains("cannot be null");
+    }
+
+    @Test
+    public void testSetFadeableUsages_withEmptyListClears_addsNewUsage() {
+        final List<Integer> fadeableUsages = List.of(
+                AudioAttributes.USAGE_VOICE_COMMUNICATION,
+                AudioAttributes.USAGE_ALARM,
+                AudioAttributes.USAGE_ASSISTANT
+        );
+        FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder()
+                .setFadeableUsages(fadeableUsages);
+
+        fmcBuilder.setFadeableUsages(List.of());
+
+        FadeManagerConfiguration fmc = fmcBuilder
+                .addFadeableUsage(AudioAttributes.USAGE_MEDIA)
+                .setFadeOutVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG).build();
+        expect.withMessage("Fadeable usages set to empty list")
+                .that(fmc.getFadeableUsages()).isEqualTo(List.of(AudioAttributes.USAGE_MEDIA));
+    }
+
+
+    @Test
+    public void testAddFadeableUsage() {
+        final int usageToAdd = AudioAttributes.USAGE_ASSISTANT;
+        AudioAttributes aaToAdd = createAudioAttributesForUsage(usageToAdd);
+        List<Integer> updatedUsages = new ArrayList<>(mFmc.getFadeableUsages());
+        updatedUsages.add(usageToAdd);
+
+        FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration
+                .Builder(mFmc).addFadeableUsage(usageToAdd)
+                .setFadeOutVolumeShaperConfigForAudioAttributes(aaToAdd,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForAudioAttributes(aaToAdd,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+                .build();
+
+        expect.withMessage("Fadeable usages").that(updatedFmc.getFadeableUsages())
+                .containsExactlyElementsIn(updatedUsages);
+    }
+
+    @Test
+    public void testAddFadeableUsage_withoutSetFadeableUsages() {
+        final int newUsage = AudioAttributes.USAGE_ASSISTANT;
+        AudioAttributes aaToAdd = createAudioAttributesForUsage(newUsage);
+
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .addFadeableUsage(newUsage)
+                .setFadeOutVolumeShaperConfigForAudioAttributes(aaToAdd,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForAudioAttributes(aaToAdd,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+                .build();
+
+        expect.withMessage("Fadeable usages").that(fmc.getFadeableUsages())
+                .containsExactlyElementsIn(List.of(newUsage));
+    }
+
+    @Test
+    public void testAddFadeableUsage_withInvalidUsage_fails() {
+        List<Integer> setUsages = Arrays.asList(
+                AudioAttributes.USAGE_VOICE_COMMUNICATION,
+                AudioAttributes.USAGE_ASSISTANT
+        );
+        AudioAttributes aaForVoiceComm = createAudioAttributesForUsage(
+                AudioAttributes.USAGE_VOICE_COMMUNICATION);
+        AudioAttributes aaForAssistant = createAudioAttributesForUsage(
+                AudioAttributes.USAGE_ASSISTANT);
+        FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder()
+                .setFadeableUsages(setUsages)
+                .setFadeOutVolumeShaperConfigForAudioAttributes(aaForVoiceComm,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForAudioAttributes(aaForVoiceComm,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG)
+                .setFadeOutVolumeShaperConfigForAudioAttributes(aaForAssistant,
+                        TEST_FADE_OUT_VOLUME_SHAPER_CONFIG)
+                .setFadeInVolumeShaperConfigForAudioAttributes(aaForAssistant,
+                        TEST_FADE_IN_VOLUME_SHAPER_CONFIG);
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                fmcBuilder.addFadeableUsage(TEST_INVALID_USAGE)
+        );
+
+        FadeManagerConfiguration fmc = fmcBuilder.build();
+        expect.withMessage("Fadeable usages ").that(thrown).hasMessageThat()
+                .contains("Invalid usage");
+        expect.withMessage("Fadeable usages").that(fmc.getFadeableUsages())
+                .containsExactlyElementsIn(setUsages);
+    }
+
+    @Test
+    public void testClearFadeableUsage() {
+        final int usageToClear = AudioAttributes.USAGE_MEDIA;
+        List<Integer> updatedUsages = new ArrayList<>(mFmc.getFadeableUsages());
+        updatedUsages.remove((Integer) usageToClear);
+
+        FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration
+                .Builder(mFmc).clearFadeableUsage(usageToClear).build();
+
+        expect.withMessage("Clear fadeable usage").that(updatedFmc.getFadeableUsages())
+                .containsExactlyElementsIn(updatedUsages);
+    }
+
+    @Test
+    public void testClearFadeableUsage_withInvalidUsage_fails() {
+        FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc);
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                fmcBuilder.clearFadeableUsage(TEST_INVALID_USAGE)
+        );
+
+        FadeManagerConfiguration fmc = fmcBuilder.build();
+        expect.withMessage("Clear invalid usage").that(thrown).hasMessageThat()
+                .contains("Invalid usage");
+        expect.withMessage("Fadeable usages").that(fmc.getFadeableUsages())
+                .containsExactlyElementsIn(mFmc.getFadeableUsages());
+    }
+
+    @Test
+    public void testSetUnfadeableContentTypes() {
+        final List<Integer> unfadeableContentTypes = List.of(
+                AudioAttributes.CONTENT_TYPE_MOVIE,
+                AudioAttributes.CONTENT_TYPE_SONIFICATION
+        );
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setUnfadeableContentTypes(unfadeableContentTypes).build();
+
+        expect.withMessage("Unfadeable content types set")
+                .that(fmc.getUnfadeableContentTypes()).isEqualTo(unfadeableContentTypes);
+    }
+
+    @Test
+    public void testSetUnfadeableContentTypes_withInvalidContentType_fails() {
+        final List<Integer> invalidUnfadeableContentTypes = List.of(
+                AudioAttributes.CONTENT_TYPE_MOVIE,
+                TEST_INVALID_CONTENT_TYPE,
+                AudioAttributes.CONTENT_TYPE_SONIFICATION
+        );
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                new FadeManagerConfiguration.Builder()
+                        .setUnfadeableContentTypes(invalidUnfadeableContentTypes).build()
+        );
+
+        expect.withMessage("Invalid content type set exception").that(thrown).hasMessageThat()
+                .contains("Invalid content type");
+    }
+
+    @Test
+    public void testSetUnfadeableContentTypes_withNullContentType_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new FadeManagerConfiguration.Builder()
+                        .setUnfadeableContentTypes(/* contentType= */ null).build()
+        );
+
+        expect.withMessage("Null content type set exception").that(thrown).hasMessageThat()
+                .contains("cannot be null");
+    }
+
+    @Test
+    public void testSetUnfadeableContentTypes_withEmptyList_clearsExistingList() {
+        final List<Integer> unfadeableContentTypes = List.of(
+                AudioAttributes.CONTENT_TYPE_MOVIE,
+                AudioAttributes.CONTENT_TYPE_SONIFICATION
+        );
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setUnfadeableContentTypes(unfadeableContentTypes).build();
+
+        FadeManagerConfiguration fmcWithEmptyLsit = new FadeManagerConfiguration.Builder(fmc)
+                .setUnfadeableContentTypes(List.of()).build();
+
+        expect.withMessage("Unfadeable content types for empty list")
+                .that(fmcWithEmptyLsit.getUnfadeableContentTypes()).isEmpty();
+    }
+
+    @Test
+    public void testAddUnfadeableContentType() {
+        final int contentTypeToAdd = AudioAttributes.CONTENT_TYPE_MOVIE;
+        List<Integer> upatdedContentTypes = new ArrayList<>(mFmc.getUnfadeableContentTypes());
+        upatdedContentTypes.add(contentTypeToAdd);
+
+        FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration
+                .Builder(mFmc).addUnfadeableContentType(contentTypeToAdd).build();
+
+        expect.withMessage("Unfadeable content types").that(updatedFmc.getUnfadeableContentTypes())
+                .containsExactlyElementsIn(upatdedContentTypes);
+    }
+
+    @Test
+    public void testAddUnfadeableContentTypes_withoutSetUnfadeableContentTypes() {
+        final int newContentType = AudioAttributes.CONTENT_TYPE_MOVIE;
+
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .addUnfadeableContentType(newContentType).build();
+
+        expect.withMessage("Unfadeable content types").that(fmc.getUnfadeableContentTypes())
+                .containsExactlyElementsIn(List.of(newContentType));
+    }
+
+    @Test
+    public void testAddunfadeableContentTypes_withInvalidContentType_fails() {
+        final List<Integer> unfadeableContentTypes = List.of(
+                AudioAttributes.CONTENT_TYPE_MOVIE,
+                AudioAttributes.CONTENT_TYPE_SONIFICATION
+        );
+        FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder()
+                .setUnfadeableContentTypes(unfadeableContentTypes);
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                fmcBuilder.addUnfadeableContentType(TEST_INVALID_CONTENT_TYPE).build()
+        );
+
+        expect.withMessage("Invalid content types exception").that(thrown).hasMessageThat()
+                .contains("Invalid content type");
+    }
+
+    @Test
+    public void testClearUnfadeableContentType() {
+        List<Integer> unfadeableContentTypes = new ArrayList<>(Arrays.asList(
+                AudioAttributes.CONTENT_TYPE_MOVIE,
+                AudioAttributes.CONTENT_TYPE_SONIFICATION
+        ));
+        final int contentTypeToClear = AudioAttributes.CONTENT_TYPE_MOVIE;
+
+        FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder()
+                .setUnfadeableContentTypes(unfadeableContentTypes)
+                .clearUnfadeableContentType(contentTypeToClear).build();
+
+        unfadeableContentTypes.remove((Integer) contentTypeToClear);
+        expect.withMessage("Unfadeable content types").that(updatedFmc.getUnfadeableContentTypes())
+                .containsExactlyElementsIn(unfadeableContentTypes);
+    }
+
+    @Test
+    public void testClearUnfadeableContentType_withInvalidContentType_fails() {
+        FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc);
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
+                fmcBuilder.clearUnfadeableContentType(TEST_INVALID_CONTENT_TYPE).build()
+        );
+
+        expect.withMessage("Invalid content type exception").that(thrown).hasMessageThat()
+                .contains("Invalid content type");
+    }
+
+    @Test
+    public void testSetUnfadeableUids() {
+        final List<Integer> unfadeableUids = List.of(
+                TEST_UID_1,
+                TEST_UID_2
+        );
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setUnfadeableUids(unfadeableUids).build();
+
+        expect.withMessage("Unfadeable uids set")
+                .that(fmc.getUnfadeableUids()).isEqualTo(unfadeableUids);
+    }
+
+    @Test
+    public void testSetUnfadeableUids_withNullUids_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new FadeManagerConfiguration.Builder()
+                        .setUnfadeableUids(/* uids= */ null).build()
+        );
+
+        expect.withMessage("Null unfadeable uids").that(thrown).hasMessageThat()
+                .contains("cannot be null");
+    }
+
+    @Test
+    public void testAddUnfadeableUid() {
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .addUnfadeableUid(TEST_UID_1).build();
+
+        expect.withMessage("Unfadeable uids")
+                .that(fmc.getUnfadeableUids()).isEqualTo(List.of(TEST_UID_1));
+    }
+
+    @Test
+    public void testClearUnfadebaleUid() {
+        final List<Integer> unfadeableUids = List.of(
+                TEST_UID_1,
+                TEST_UID_2
+        );
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setUnfadeableUids(unfadeableUids).build();
+
+        FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder(fmc)
+                .clearUnfadeableUid(TEST_UID_1).build();
+
+        expect.withMessage("Unfadeable uids").that(updatedFmc.getUnfadeableUids())
+                .isEqualTo(List.of(TEST_UID_2));
+    }
+
+    @Test
+    public void testSetUnfadeableAudioAttributes() {
+        final List<AudioAttributes> unfadeableAttrs = List.of(
+                TEST_ASSISTANT_AUDIO_ATTRIBUTE,
+                TEST_NAVIGATION_AUDIO_ATTRIBUTE
+        );
+
+        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+                .setUnfadeableAudioAttributes(unfadeableAttrs).build();
+
+        expect.withMessage("Unfadeable audio attributes")
+                .that(fmc.getUnfadeableAudioAttributes()).isEqualTo(unfadeableAttrs);
+    }
+
+    @Test
+    public void testSetUnfadeableAudioAttributes_withNullAttributes_fails() {
+        NullPointerException thrown = assertThrows(NullPointerException.class, () ->
+                new FadeManagerConfiguration.Builder()
+                        .setUnfadeableAudioAttributes(/* attrs= */ null).build()
+        );
+
+        expect.withMessage("Null audio attributes exception").that(thrown).hasMessageThat()
+                .contains("cannot be null");
+    }
+
+    @Test
+    public void testWriteToParcel_andCreateFromParcel() {
+        Parcel parcel = Parcel.obtain();
+
+        mFmc.writeToParcel(parcel, TEST_PARCEL_FLAGS);
+        parcel.setDataPosition(/* position= */ 0);
+        expect.withMessage("Fade manager configuration write to and create from parcel")
+                .that(mFmc)
+                .isEqualTo(FadeManagerConfiguration.CREATOR.createFromParcel(parcel));
+    }
+
+    private static AudioAttributes createAudioAttributesForUsage(int usage) {
+        if (AudioAttributes.isSystemUsage(usage)) {
+            return new AudioAttributes.Builder().setSystemUsage(usage).build();
+        }
+        return new AudioAttributes.Builder().setUsage(usage).build();
+    }
+}
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 8f60c97..1c8a8d5 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -321,7 +321,7 @@
     <!-- Dialog body shown when the user is trying to restore an app but the installer responsible
          for the action is in a disabled state. [CHAR LIMIT=none] -->
     <string name="unarchive_error_installer_disabled_body">
-        To restore this app, enable the
+        To restore this app, enable
         <xliff:g id="installername" example="App Store">%1$s</xliff:g> in Settings
     </string>
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index 4e28d77..09be768 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -116,6 +116,7 @@
 
                 int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
                 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
+                flags |= getIntent().getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0);
 
                 createContextAsUser(user, 0).getPackageManager().getPackageInstaller().uninstall(
                         new VersionedPackage(mAppInfo.packageName,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index ba627e9..5c9b728 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -19,6 +19,7 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.content.pm.Flags.usePiaV2;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
 
 import android.Manifest;
@@ -55,8 +56,8 @@
 import com.android.packageinstaller.television.ErrorFragment;
 import com.android.packageinstaller.television.UninstallAlertFragment;
 import com.android.packageinstaller.television.UninstallAppProgress;
-
 import com.android.packageinstaller.v2.ui.UninstallLaunch;
+
 import java.util.List;
 
 /*
@@ -76,6 +77,7 @@
         public boolean allUsers;
         public UserHandle user;
         public PackageManager.UninstallCompleteCallback callback;
+        public int deleteFlags;
     }
 
     private String mPackageName;
@@ -226,10 +228,26 @@
                 // Continue as the ActivityInfo isn't critical.
             }
         }
+        parseDeleteFlags(intent);
 
         showConfirmationDialog();
     }
 
+    /**
+     * Parses specific {@link android.content.pm.PackageManager.DeleteFlags} from {@link Intent}
+     * to archive an app if requested.
+     *
+     * Do not parse any flags because developers might pass here any flags which might cause
+     * unintended behaviour.
+     * For more context {@link com.android.server.pm.PackageArchiver#requestArchive}.
+     */
+    private void parseDeleteFlags(Intent intent) {
+        int deleteFlags = intent.getIntExtra(PackageInstaller.EXTRA_DELETE_FLAGS, 0);
+        int archive = deleteFlags & PackageManager.DELETE_ARCHIVE;
+        int keepData = deleteFlags & PackageManager.DELETE_KEEP_DATA;
+        mDialogInfo.deleteFlags = archive | keepData;
+    }
+
     public DialogInfo getDialogInfo() {
         return mDialogInfo;
     }
@@ -347,7 +365,10 @@
             if (returnResult || getCallingActivity() != null) {
                 newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
             }
-
+            if (mDialogInfo.deleteFlags != 0) {
+                newIntent.putExtra(PackageInstaller.EXTRA_DELETE_FLAGS,
+                        mDialogInfo.deleteFlags);
+            }
             startActivity(newIntent);
         } else {
             int uninstallId;
@@ -393,6 +414,7 @@
 
                 int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
                 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
+                flags |= mDialogInfo.deleteFlags;
 
                 createContextAsUser(mDialogInfo.user, 0).getPackageManager().getPackageInstaller()
                         .uninstall(new VersionedPackage(mDialogInfo.appInfo.packageName,
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index b40e911..9703c347 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.1.4"
+agp = "8.2.0"
 compose-compiler = "1.5.1"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
index 033e24c..d64cd49 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index ce89de6..516749d 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,7 +16,7 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/SettingsLib/Spa/gradlew b/packages/SettingsLib/Spa/gradlew
index fcb6fca..1aa94a4 100755
--- a/packages/SettingsLib/Spa/gradlew
+++ b/packages/SettingsLib/Spa/gradlew
@@ -83,7 +83,8 @@
 # This is normally unused
 # shellcheck disable=SC2034
 APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD=maximum
@@ -144,7 +145,7 @@
     case $MAX_FD in #(
       max*)
         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045
+        # shellcheck disable=SC2039,SC3045
         MAX_FD=$( ulimit -H -n ) ||
             warn "Could not query maximum file descriptor limit"
     esac
@@ -152,7 +153,7 @@
       '' | soft) :;; #(
       *)
         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045
+        # shellcheck disable=SC2039,SC3045
         ulimit -n "$MAX_FD" ||
             warn "Could not set maximum file descriptor limit to $MAX_FD"
     esac
@@ -201,11 +202,11 @@
 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 
-# Collect all arguments for the java command;
-#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-#     shell script including quotes and variable substitutions, so put them in
-#     double quotes to make sure that they get re-expanded; and
-#   * put everything else in single quotes, so that it's not re-expanded.
+# Collect all arguments for the java command:
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+#     and any embedded shellness will be escaped.
+#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+#     treated as '${Hostname}' itself on the command line.
 
 set -- \
         "-Dorg.gradle.appname=$APP_BASE_NAME" \
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index de2cf1f..81a8b324 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -19,11 +19,11 @@
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import android.graphics.drawable.Drawable
+import android.util.IconDrawableFactory
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.produceState
 import androidx.compose.ui.platform.LocalContext
-import com.android.settingslib.Utils
 import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.framework.common.userManager
@@ -65,6 +65,7 @@
 
 internal class AppRepositoryImpl(private val context: Context) : AppRepository {
     private val packageManager = context.packageManager
+    private val iconDrawableFactory = IconDrawableFactory.newInstance(context)
 
     override fun loadLabel(app: ApplicationInfo): String = app.loadLabel(packageManager).toString()
 
@@ -72,7 +73,7 @@
     override fun produceIcon(app: ApplicationInfo) =
         produceState<Drawable?>(initialValue = null, app) {
             withContext(Dispatchers.IO) {
-                value = Utils.getBadgedIcon(context, app)
+                value = iconDrawableFactory.getBadgedIcon(app)
             }
         }
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
index 8f458d3..70e4055 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
@@ -27,14 +27,12 @@
 import com.android.settingslib.spa.testutils.delay
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Spy
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
@@ -42,23 +40,14 @@
     @get:Rule
     val composeTestRule = createComposeRule()
 
-    @get:Rule
-    val mockito: MockitoRule = MockitoJUnit.rule()
+    private val userManager = mock<UserManager>()
 
-    @Spy
-    private val context: Context = ApplicationProvider.getApplicationContext()
-
-    @Mock
-    private lateinit var userManager: UserManager
-
-    private lateinit var appRepository: AppRepositoryImpl
-
-    @Before
-    fun setUp() {
-        whenever(context.userManager).thenReturn(userManager)
-        appRepository = AppRepositoryImpl(context)
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { userManager } doReturn userManager
     }
 
+    private val appRepository = AppRepositoryImpl(context)
+
     @Test
     fun produceIconContentDescription_workProfile() {
         whenever(userManager.isManagedProfile(APP.userId)).thenReturn(true)
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 079cde0..8e1067f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -81,6 +81,8 @@
 import java.util.UUID;
 import java.util.regex.Pattern;
 
+import javax.annotation.Nullable;
+
 /**
  * Keeps track of information about all installed applications, lazy-loading
  * as needed.
@@ -492,7 +494,8 @@
                 ApplicationInfo info = getAppInfoLocked(packageName, userId);
                 if (info == null) {
                     try {
-                        info = mIpm.getApplicationInfo(packageName, 0, userId);
+                        info = mIpm.getApplicationInfo(packageName,
+                                PackageManager.MATCH_ARCHIVED_PACKAGES, userId);
                     } catch (RemoteException e) {
                         Log.w(TAG, "getEntry couldn't reach PackageManager", e);
                         return null;
@@ -1612,7 +1615,7 @@
     }
 
     public static class AppEntry extends SizeInfo {
-        public final File apkFile;
+        @Nullable public final File apkFile;
         public final long id;
         public String label;
         public long size;
@@ -1671,7 +1674,7 @@
 
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         public AppEntry(Context context, ApplicationInfo info, long id) {
-            apkFile = new File(info.sourceDir);
+            this.apkFile = info.sourceDir != null ? new File(info.sourceDir) : null;
             this.id = id;
             this.info = info;
             this.size = SIZE_UNKNOWN;
@@ -1717,13 +1720,13 @@
 
         public void ensureLabel(Context context) {
             if (this.label == null || !this.mounted) {
-                if (!this.apkFile.exists()) {
-                    this.mounted = false;
-                    this.label = info.packageName;
-                } else {
+                if (this.apkFile != null  && this.apkFile.exists()) {
                     this.mounted = true;
                     CharSequence label = info.loadLabel(context.getPackageManager());
                     this.label = label != null ? label.toString() : info.packageName;
+                } else {
+                    this.mounted = false;
+                    this.label = info.packageName;
                 }
             }
         }
@@ -1738,7 +1741,7 @@
             }
 
             if (this.icon == null) {
-                if (this.apkFile.exists()) {
+                if (this.apkFile != null && this.apkFile.exists()) {
                     this.icon = Utils.getBadgedIcon(context, info);
                     return true;
                 } else {
@@ -1748,7 +1751,7 @@
             } else if (!this.mounted) {
                 // If the app wasn't mounted but is now mounted, reload
                 // its icon.
-                if (this.apkFile.exists()) {
+                if (this.apkFile != null && this.apkFile.exists()) {
                     this.mounted = true;
                     this.icon = Utils.getBadgedIcon(context, info);
                     return true;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 650319f..b6a0c7b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -442,6 +442,9 @@
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
     <uses-permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS" />
 
+    <!-- Permission required for CTS test - CtsNfcResolverDerviceTest -->
+    <uses-permission android:name="android.permission.SHOW_CUSTOMIZED_RESOLVER" />
+
     <!-- Permission needed for CTS test - MusicRecognitionManagerTest -->
     <uses-permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" />
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 2c4dc80..185a06c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -7,6 +7,7 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.width
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Close
@@ -19,8 +20,10 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.Edge
 import com.android.compose.animation.scene.ElementKey
@@ -28,6 +31,7 @@
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.SceneTransitionLayoutState
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.transitions
@@ -56,6 +60,7 @@
  * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
  * handling and transitions before the full Flexiglass layout is ready.
  */
+@OptIn(ExperimentalComposeUiApi::class)
 @Composable
 fun CommunalContainer(
     modifier: Modifier = Modifier,
@@ -65,6 +70,7 @@
         viewModel.currentScene
             .transform<CommunalSceneKey, SceneKey> { value -> value.toTransitionSceneKey() }
             .collectAsState(TransitionSceneKey.Blank)
+    val sceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) }
     // Don't show hub mode UI if keyguard is present. This is important since we're in the shade,
     // which can be opened from many locations.
     val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
@@ -75,32 +81,59 @@
         return
     }
 
-    SceneTransitionLayout(
-        modifier = modifier.fillMaxSize(),
-        currentScene = currentScene,
-        onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) },
-        transitions = sceneTransitions,
-        edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize)
-    ) {
-        scene(
-            TransitionSceneKey.Blank,
-            userActions =
-                mapOf(
-                    Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal
-                )
+    Box(modifier = modifier.fillMaxSize()) {
+        SceneTransitionLayout(
+            modifier = Modifier.fillMaxSize(),
+            currentScene = currentScene,
+            onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) },
+            transitions = sceneTransitions,
+            state = sceneTransitionLayoutState,
+            edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize)
         ) {
-            BlankScene { showSceneTransitionLayout = false }
+            scene(
+                TransitionSceneKey.Blank,
+                userActions =
+                    mapOf(
+                        Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to
+                            TransitionSceneKey.Communal
+                    )
+            ) {
+                BlankScene { showSceneTransitionLayout = false }
+            }
+
+            scene(
+                TransitionSceneKey.Communal,
+                userActions =
+                    mapOf(
+                        Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to
+                            TransitionSceneKey.Blank
+                    ),
+            ) {
+                CommunalScene(viewModel, modifier = modifier)
+            }
         }
 
-        scene(
-            TransitionSceneKey.Communal,
-            userActions =
-                mapOf(
-                    Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank
-                ),
-        ) {
-            CommunalScene(viewModel, modifier = modifier)
-        }
+        // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't
+        //  block touches anymore.
+        Box(
+            modifier =
+                Modifier.fillMaxSize()
+                    // Offsetting to the left so that edge swipe to open the hub still works. This
+                    // does mean that the very right edge of the hub won't refresh the screen
+                    // timeout, but should be good enough for a temporary solution.
+                    .offset(x = -ContainerDimensions.EdgeSwipeSize)
+                    .pointerInteropFilter {
+                        viewModel.onUserActivity()
+                        if (
+                            sceneTransitionLayoutState.transitionState.currentScene ==
+                                TransitionSceneKey.Blank
+                        ) {
+                            viewModel.onOuterTouch(it)
+                            return@pointerInteropFilter true
+                        }
+                        false
+                    }
+        )
     }
 }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 651594c..cdd074d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -30,15 +30,15 @@
 import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.log.core.MessageInitializer
 import com.android.systemui.log.core.MessagePrinter
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockId
-import com.android.systemui.plugins.ClockMetadata
-import com.android.systemui.plugins.ClockProvider
-import com.android.systemui.plugins.ClockProviderPlugin
-import com.android.systemui.plugins.ClockSettings
 import com.android.systemui.plugins.PluginLifecycleManager
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockProvider
+import com.android.systemui.plugins.clocks.ClockProviderPlugin
+import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.util.Assert
 import java.io.PrintWriter
 import java.util.concurrent.ConcurrentHashMap
@@ -173,8 +173,10 @@
                     { "Skipping initial load of known clock package package: $str1" }
                 )
 
+                var isCurrentClock = false
                 var isClockListChanged = false
                 for (metadata in knownClocks) {
+                    isCurrentClock = isCurrentClock || currentClockId == metadata.clockId
                     val id = metadata.clockId
                     val info =
                         availableClocks.concurrentGetOrPut(id, ClockInfo(metadata, null, manager)) {
@@ -207,8 +209,9 @@
                 }
                 verifyLoadedProviders()
 
-                // Load executed via verifyLoadedProviders
-                return false
+                // Load immediately if it's the current clock, otherwise let verifyLoadedProviders
+                // load and unload clocks as necessary on the background thread.
+                return isCurrentClock
             }
 
             override fun onPluginLoaded(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 5375591..141e1c1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -25,16 +25,18 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.customization.R
 import com.android.systemui.log.core.MessageBuffer
-import com.android.systemui.plugins.ClockAnimations
-import com.android.systemui.plugins.ClockConfig
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockEvents
-import com.android.systemui.plugins.ClockFaceConfig
-import com.android.systemui.plugins.ClockFaceController
-import com.android.systemui.plugins.ClockFaceEvents
-import com.android.systemui.plugins.ClockSettings
-import com.android.systemui.plugins.DefaultClockFaceLayout
-import com.android.systemui.plugins.WeatherData
+import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockConfig
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFaceConfig
+import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockSettings
+import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.plugins.clocks.ZenData
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
@@ -256,6 +258,8 @@
         }
 
         override fun onWeatherDataChanged(data: WeatherData) {}
+        override fun onAlarmDataChanged(data: AlarmData) {}
+        override fun onZenDataChanged(data: ZenData) {}
     }
 
     open inner class DefaultClockAnimations(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index f819da5..a219be5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -18,11 +18,11 @@
 import android.graphics.drawable.Drawable
 import android.view.LayoutInflater
 import com.android.systemui.customization.R
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockId
-import com.android.systemui.plugins.ClockMetadata
-import com.android.systemui.plugins.ClockProvider
-import com.android.systemui.plugins.ClockSettings
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockProvider
+import com.android.systemui.plugins.clocks.ClockSettings
 
 private val TAG = DefaultClockProvider::class.simpleName
 const val DEFAULT_CLOCK_ID = "DEFAULT"
diff --git a/packages/SystemUI/docs/clock-plugins.md b/packages/SystemUI/docs/clock-plugins.md
index 9cb115a..fee82df 100644
--- a/packages/SystemUI/docs/clock-plugins.md
+++ b/packages/SystemUI/docs/clock-plugins.md
@@ -12,7 +12,7 @@
 clock controller.
 
 ### Clock Library Code
-[ClockProvider and ClockController](../plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt)
+[ClockProvider and ClockController](../plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt)
 serve as the interface between the lockscreen (or other host application) and the clock that is
 being rendered. Implementing these interfaces is the primary integration point for rendering clocks
 in SystemUI. Many of the methods have an empty default implementation and are optional for
@@ -29,12 +29,12 @@
 
 The [ClockRegistry](../customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt)
 determines which clock should be shown, and handles creating them. It does this by maintaining a
-list of [ClockProviders](../plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt) and
+list of [ClockProviders](../plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt) and
 delegating work to them as appropriate. The DefaultClockProvider is compiled in so that it is
 guaranteed to be available, and additional ClockProviders are loaded at runtime via
 [PluginManager](../plugin_core/src/com/android/systemui/plugins/PluginManager.java).
 
-[ClockPlugin](../plugin/src/com/android/systemui/plugins/ClockPlugin.java) is deprecated and no
+[ClockPlugin](../plugin/src/com/android/systemui/plugins/clocks/ClockPlugin.java) is deprecated and no
 longer used by keyguard to render clocks. The host code has been disabled but most of it is still
 present in the source tree, although it will likely be removed in a later patch.
 
diff --git a/packages/SystemUI/docs/plugin_hooks.md b/packages/SystemUI/docs/plugin_hooks.md
index 6ce7ee0..bfccbac 100644
--- a/packages/SystemUI/docs/plugin_hooks.md
+++ b/packages/SystemUI/docs/plugin_hooks.md
@@ -52,7 +52,7 @@
 Use: Control over swipes/input for notification views, can be used to control what happens when you swipe/long-press
 
 ### Action: com.android.systemui.action.PLUGIN_CLOCK_PROVIDER
-Expected interface: [ClockProviderPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt)
+Expected interface: [ClockProviderPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt)
 
 Use: Allows replacement of the keyguard main clock. See [additional Documentation](./clock-plugins.md).
 
diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md
index 4313f44..ee388ec 100644
--- a/packages/SystemUI/docs/qs-tiles.md
+++ b/packages/SystemUI/docs/qs-tiles.md
@@ -111,16 +111,15 @@
 * **[`QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**:
   Abstract class that provides basic Tile functionality. These allows
   external [Factories](#qsfactory) to create Tiles.
-* **[`QSTileViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.java)
-  **: Implementation of `QSTileView`. It takes care of the following:
+* **[`QSTileViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.java)**:
+  Implementation of `QSTileView`. It takes care of the following:
     * Holding the icon
     * Background color and shape
     * Ripple
     * Click listening
     * Labels
 * **[`QSIconView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java)**
-* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)
-  **
+* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)**
 
 #### QSIconView and QSIconViewImpl
 
@@ -333,18 +332,21 @@
 ## How are tiles created/instantiated?
 
 This section describes the classes that aid in the creation of each tile as well as the complete
-lifecycle of a tile. First we describe two important interfaces/classes.
+lifecycle of a tile. The current system makes use of flows to propagate information downstream.
 
-### QSTileHost
+First we describe three important interfaces/classes.
 
-This class keeps track of the tiles selected by the current user (backed in the Secure
-Setting `sysui_qs_tiles`) to be displayed in Quick Settings. Whenever the value of this setting
-changes (or on device start), the whole list of tiles is read. This is compared with the current
-tiles, destroying unnecessary ones and creating needed ones.
+### TileSpecRepository (and UserTileSpecRepository)
 
-It additionally provides a point of communication between the tiles and the StatusBar, for example
-to open it and collapse it. And a way for the StatusBar service to add tiles (only works
-for `CustomTile`).
+These classes keep track of the current tiles for each user, as a list of Tile specs. While the
+device is running, this is the source of truth of tiles for that user.
+
+The list is persisted to `Settings.Secure` every time it changes so it will be available upon
+restart or backup. In particular, any changes in the secure setting while this repository is
+tracking the list of tiles will be reverted.
+
+The class provides a `Flow<List<TileSpec>>` for each user that can be collected to keep track of the
+current list of tiles.
 
 #### Tile specs
 
@@ -356,36 +358,49 @@
 or `battery`). Custom tile specs are always a string of the form `custom(...)` where the ellipsis is
 a flattened String representing the `ComponentName` for the corresponding `TileService`.
 
+We represent these internally using a `TileSpec` class that can distinguish between platform tiles
+and custom tiles.
+
+### CurrentTilesInteractor
+
+This class consumes the lists of specs provided by `TileSpecRepository` and produces a
+`Flow<List<Pair<TileSpec, QSTile>>>` with the current tiles for the current user.
+
+Internally, whenever the list of tiles changes, the following operation is performed:
+* Properly dispose of tiles that are no longer in the current list.
+* Properly dispose of tiles that are no longer available.
+* If the user has changed, relay the new user to the platform tiles and destroy any custom tiles.
+* Create new tiles as needed, disposing those that are not available or when the corresponding
+  service does not exist.
+* Reorder the tiles.
+
+Also, when this is completed, we pass the final list back to the repository so it matches the
+correct list of tiles.
+
 ### QSFactory
 
 This interface provides a way of creating tiles and views from a spec. It can be used in plugins to
 provide different definitions for tiles.
 
 In SystemUI there is only one implementation of this factory and that is the default
-factory (`QSFactoryImpl`) in `QSTileHost`.
+factory (`QSFactoryImpl`) in `CurrentTilesInteractorImpl`.
 
 #### QSFactoryImpl
 
-This class implements two methods as specified in the `QSFactory` interface:
+This class implements the following method as specified in the `QSFactory` interface:
 
 * ```java
   public QSTile createTile(String)
   ```
 
-  Creates a tile (backend) from a given spec. The factory has providers for all of the SystemUI
-  tiles, returning one when the correct spec is used.
+  Creates a tile (backend) from a given spec. The factory has a map with providers for all of the
+  SystemUI tiles, returning one when the correct spec is used.
 
   If the spec is not recognized but it has the `custom(` prefix, the factory tries to create
-  a `CustomTile` for the component in the spec. This could fail (the component is not a
-  valid `TileService` or is not enabled) and will be detected later when the tile is polled to
-  determine if it's available.
+  a `CustomTile` for the component in the spec.
 
-* ```java
-  public QSTileView createTileView(QSTile, boolean)
-  ```
-
-  Creates a view for the corresponding `QSTile`. The second parameter determines if the view that is
-  created should be a collapsed one (for using in QQS) or not (for using in QS).
+  As part of filtering not valid tiles, custom tiles that don't have a corresponding valid service
+  component are never instantiated.
 
 ### Lifecycle of a Tile
 
@@ -393,20 +408,20 @@
 tiles. Following that, there will be a section with the steps that are exclusive to third party
 tiles.
 
-1. The tile is added through the QS customizer by the user. This will immediately save the new list
-   of tile specs to the Secure Setting `sysui_qs_tiles`. This step could also happend if `StatusBar`
-   adds tiles (either through adb, or through its service interface as with the `DevelopmentTiles`).
-2. This triggers a "setting changed" that is caught by `QSTileHost`. This class processes the new
-   value of the setting and finds out that there is a new spec in the list. Alternatively, when the
-   device is booted, all tiles in the setting are considered as "new".
-3. `QSTileHost` calls all the available `QSFactory` classes that it has registered in order to find
-   the first one that will be able to create a tile with that spec. Assume that `QSFactoryImpl`
-   managed to create the tile, which is some implementation of `QSTile` (either a SystemUI subclass
-   of `QSTileImpl` or a `CustomTile`). If the tile is available, it's stored in a map and things
-   proceed forward.
-4. `QSTileHost` calls its callbacks indicating that the tiles have changed. In particular, `QSPanel`
-   and `QuickQSPanel` receive this call with the full list of tiles. We will focus on these two
-   classes.
+1. The tile is added through the QS customizer by the user. This will send the new list of tiles to
+   `TileSpecRepository` which will update its internal state and also store the new value in the
+   secure setting `sysui_qs_tiles`. This step could also happen if `StatusBar` adds tiles (either
+   through adb, or through its service interface as with the `DevelopmentTiles`).
+2. This updates the flow that `CurrentTilesInteractor` is collecting from, triggering the process
+   described above.
+3. `CurrentTilesInteractor` calls the available `QSFactory` classes in order to find one that will
+   be able to create a tile with that spec. Assuming that `QSFactoryImpl` managed to create the
+   tile, which is some implementation of `QSTile` (either a SystemUI subclass
+   of `QSTileImpl` or a `CustomTile`) it will be added to the current list.
+   If the tile is available, it's stored in a map and things proceed forward.
+4. `CurrentTilesInteractor` updates its flow and classes collecting from it will be notified of the
+   change. In particular, `QSPanel` and `QuickQSPanel` receive this call with the full list of
+   tiles. We will focus on these two classes.
 5. For each tile in this list, a `QSTileView` is created (collapsed or expanded) and attached to
    a `TileRecord` containing the tile backend and the view. Additionally:
     * a callback is attached to the tile to communicate between the backend and the view or the
@@ -469,12 +484,10 @@
    that SystemUI knows how to create (to show to the user in the customization screen). The second
    one contains only the default tiles that the user will experience on a fresh boot or after they
    reset their tiles.
-6.
-In [SystemUI/res/values/tiles_states_strings.xml](/packages/SystemUI/res/values/tiles_states_strings.xml),
+6. In [SystemUI/res/values/tiles_states_strings.xml](/packages/SystemUI/res/values/tiles_states_strings.xml),
 add a new array for your tile. The name has to be `tile_states_<spec>`. Use a good description to
 help the translators.
-7.
-In [`SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt),
+7. In [`SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt),
 add a new element to the map in `SubtitleArrayMapping` corresponding to the resource created in the
 previous step.
 
@@ -569,4 +582,94 @@
 ### Implementing a third party tile
 
 For information about this, use the Android Developer documentation
-for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
\ No newline at end of file
+for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
+
+## AutoAddable tiles
+
+AutoAddable tiles are tiles that are not part of the default set, but will be automatically added
+for the user, when the user enabled a feature for the first time. For example:
+* When the user creates a work profile, the work profile tile is automatically added.
+* When the user sets up a hotspot for the first time, the hotspot tile is automatically added.
+
+In order to declare a tile as auto-addable, there are two ways:
+
+* If the tile can be tied to a secure setting such that the tile should be auto added after that
+  setting has changed to a non-zero value for the first time, a new line can be added to the
+  string-array `config_quickSettingsAutoAdd` in [config.xml](/packages/SystemUI/res/values/config.xml).
+* If more specific behavior is needed, a new
+  [AutoAddable](/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddable.kt)
+  can be added in the `autoaddables` package. This can have custom logic that produces a flow of
+  signals on when the tile should be auto-added (or auto-removed in special cases).
+
+  *Special case: If the data comes from a `CallbackController`, a special
+  `CallbackControllerAutoAddable` can be created instead that handles a lot of the common code.*
+
+### AutoAddRepository (and UserAutoAddRepository)
+
+These classes keep track of tiles that have been auto-added for each user, as a list of Tile specs.
+While the device is running, this is the source of truth of already auto-added tiles for that user.
+
+The list is persisted to `Settings.Secure` every time it changes so it will be available upon
+restart or backup. In particular, any changes in the secure setting while this repository is
+tracking the list of tiles will be reverted.
+
+The class provides a `Flow<Set<TileSpec>>` for each user that can be collected to keep track of the
+set of already auto added tiles.
+
+### AutoAddInteractor
+
+This class collects all registered (through Dagger) `AutoAddables` and merges all the signals for
+the current user. It will add/remove tiles as necessary and mark them as such in the
+`AutoAddRepository`.
+
+## Backup and restore
+
+It's important to point out that B&R of Quick Settings tiles only concerns itself with restoring,
+for each user, the list of current tiles and their order. The state of the tiles (or other things
+that can be accessed from them like list of WiFi networks) is the concern of each feature team and
+out of the scope of Quick Settings.
+
+In order to provide better support to restoring Quick Settings tiles and prevent overwritten or
+inconsistent data, the system has the following steps:
+
+1. When `Settings.Secure.SYSUI_QS_TILES` and `Settings.Secure.QS_AUTO_TILES` are restored, a
+  broadcast is sent to SystemUI. This is handled by
+  [SettingsHelper](/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java).
+  The broadcasts are received by [QSSettingsRestoredRepository](/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt)
+  and grouped by user into a data object. As described above, the change performed by the restore in
+  settings is overriden by the corresponding repositories.
+2. Once both settings have been restored, the data is reconciled with the current data, to account
+  for tiles that may have been auto-added between the start of SystemUI and the time the restore
+  happened. The guiding principles for the reconciliation are as follows:
+    * We assume that the user expects the restored tiles to be the ones to be present after restore,
+      so those are taken as the basis for the reconciliation.
+    * Any tile that was auto-added before the restore, but had not been auto-added in the source
+      device, is auto-added again (preferably in a similar position).
+    * Any tile that was auto-added before the restore, and it was also auto-added in the source
+      device, but not present in the restored tiles, is considered removed by the user and therefore
+      not restored.
+    * Every tile that was marked as auto-added (all tiles in source + tiles added before restore)
+      are set as auto-added.
+
+## Logs for debugging
+
+The following log buffers are used for Quick Settings debugging purposes:
+
+### QSLog
+
+Logs events in the individual tiles, like listening state, clicks, and status updates.
+
+### QSTileListLog
+
+Logs changes in the current set of tiles for each user, including when tiles are created or
+destroyed, and the reason for that. It also logs what operation caused the tiles to change
+(add, remove, change, restore).
+
+### QSAutoAddLog
+
+Logs operations of auto-add (or auto-remove) of tiles.
+
+### QSRestoreLog
+
+Logs the data obtained after a successful restore of the settings. This is the data that will be
+used for reconciliation.
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index ce7db80..ea3006f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.view.viewmodel
 
 import android.app.smartspace.SmartspaceTarget
+import android.os.PowerManager
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -33,6 +34,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -50,6 +52,8 @@
 @RunWith(AndroidJUnit4::class)
 class CommunalEditModeViewModelTest : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
+    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var powerManager: PowerManager
 
     private lateinit var testScope: TestScope
 
@@ -79,6 +83,8 @@
         underTest =
             CommunalEditModeViewModel(
                 withDeps.communalInteractor,
+                shadeViewController,
+                powerManager,
                 mediaHost,
             )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 32f4d07..9bd0835 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.view.viewmodel
 
 import android.app.smartspace.SmartspaceTarget
+import android.os.PowerManager
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -33,6 +34,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -50,6 +52,8 @@
 @RunWith(AndroidJUnit4::class)
 class CommunalViewModelTest : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
+    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var powerManager: PowerManager
 
     private lateinit var testScope: TestScope
 
@@ -80,6 +84,8 @@
             CommunalViewModel(
                 withDeps.communalInteractor,
                 withDeps.tutorialInteractor,
+                shadeViewController,
+                powerManager,
                 mediaHost,
             )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
new file mode 100644
index 0000000..2b744ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.alarm.domain
+
+import android.app.AlarmManager
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
+import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import java.time.Instant
+import java.time.LocalDateTime
+import java.util.TimeZone
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlarmTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val alarmTileConfig = kosmos.qsAlarmTileConfig
+    // Using lazy (versus =) to make sure we override the right context -- see b/311612168
+    private val mapper by lazy { AlarmTileMapper(context.orCreateTestableResources.resources) }
+
+    @Test
+    fun notAlarmSet() {
+        val inputModel = AlarmTileModel.NoAlarmSet
+
+        val outputState = mapper.map(alarmTileConfig, inputModel)
+
+        val expectedState =
+            createAlarmTileState(
+                QSTileState.ActivationState.INACTIVE,
+                context.getString(R.string.qs_alarm_tile_no_alarm)
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun nextAlarmSet24HourFormat() {
+        val triggerTime = 1L
+        val inputModel =
+            AlarmTileModel.NextAlarmSet(true, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+        val outputState = mapper.map(alarmTileConfig, inputModel)
+
+        val localDateTime =
+            LocalDateTime.ofInstant(
+                Instant.ofEpochMilli(triggerTime),
+                TimeZone.getDefault().toZoneId()
+            )
+        val expectedSecondaryLabel = AlarmTileMapper.formatter24Hour.format(localDateTime)
+        val expectedState =
+            createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun nextAlarmSet12HourFormat() {
+        val triggerTime = 1L
+        val inputModel =
+            AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+        val outputState = mapper.map(alarmTileConfig, inputModel)
+
+        val localDateTime =
+            LocalDateTime.ofInstant(
+                Instant.ofEpochMilli(triggerTime),
+                TimeZone.getDefault().toZoneId()
+            )
+        val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
+        val expectedState =
+            createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createAlarmTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String
+    ): QSTileState {
+        val label = context.getString(R.string.status_bar_alarm)
+        return QSTileState(
+            { Icon.Resource(R.drawable.ic_alarm, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractorTest.kt
new file mode 100644
index 0000000..990d747
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractorTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.alarm.domain.interactor
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.os.UserHandle
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.DateFormatUtil
+import com.android.systemui.utils.leaks.FakeNextAlarmController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlarmTileDataInteractorTest : SysuiTestCase() {
+    private lateinit var dateFormatUtil: DateFormatUtil
+
+    private val nextAlarmController = FakeNextAlarmController(LeakCheck())
+    private lateinit var underTest: AlarmTileDataInteractor
+
+    @Before
+    fun setup() {
+        dateFormatUtil = mock<DateFormatUtil>()
+        underTest = AlarmTileDataInteractor(nextAlarmController, dateFormatUtil)
+    }
+
+    @Test
+    fun alarmTriggerTimeDataMatchesTheController() = runTest {
+        val expectedTriggerTime = 1L
+        val alarmInfo = AlarmManager.AlarmClockInfo(expectedTriggerTime, mock<PendingIntent>())
+        val dataList: List<AlarmTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+        nextAlarmController.setNextAlarm(alarmInfo)
+        runCurrent()
+        nextAlarmController.setNextAlarm(null)
+        runCurrent()
+
+        assertThat(dataList).hasSize(3)
+        assertThat(dataList[0]).isInstanceOf(AlarmTileModel.NoAlarmSet::class.java)
+        assertThat(dataList[1]).isInstanceOf(AlarmTileModel.NextAlarmSet::class.java)
+        val actualAlarmClockInfo = (dataList[1] as AlarmTileModel.NextAlarmSet).alarmClockInfo
+        assertThat(actualAlarmClockInfo).isNotNull()
+        val actualTriggerTime = actualAlarmClockInfo.triggerTime
+        assertThat(actualTriggerTime).isEqualTo(expectedTriggerTime)
+        assertThat(dataList[2]).isInstanceOf(AlarmTileModel.NoAlarmSet::class.java)
+    }
+
+    @Test
+    fun dateFormatUtil24HourDataMatchesController() = runTest {
+        val expectedValue = true
+        whenever(dateFormatUtil.is24HourFormat).thenReturn(expectedValue)
+        val alarmInfo = AlarmManager.AlarmClockInfo(1L, mock<PendingIntent>())
+        nextAlarmController.setNextAlarm(alarmInfo)
+
+        val model by
+            collectLastValue(
+                underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+            )
+        runCurrent()
+
+        assertThat(model).isNotNull()
+        assertThat(model).isInstanceOf(AlarmTileModel.NextAlarmSet::class.java)
+        val actualValue = (model as AlarmTileModel.NextAlarmSet).is24HourFormat
+        assertThat(actualValue).isEqualTo(expectedValue)
+    }
+
+    @Test
+    fun dateFormatUtil12HourDataMatchesController() = runTest {
+        val expectedValue = false
+        whenever(dateFormatUtil.is24HourFormat).thenReturn(expectedValue)
+        val alarmInfo = AlarmManager.AlarmClockInfo(1L, mock<PendingIntent>())
+        nextAlarmController.setNextAlarm(alarmInfo)
+
+        val model by
+            collectLastValue(
+                underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+            )
+        runCurrent()
+
+        assertThat(model).isNotNull()
+        assertThat(model).isInstanceOf(AlarmTileModel.NextAlarmSet::class.java)
+        val actualValue = (model as AlarmTileModel.NextAlarmSet).is24HourFormat
+        assertThat(actualValue).isEqualTo(expectedValue)
+    }
+
+    @Test
+    fun alwaysAvailable() = runTest {
+        val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+        assertThat(availability).hasSize(1)
+        assertThat(availability.last()).isTrue()
+    }
+
+    private companion object {
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..e44c849
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractorTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.alarm.domain.interactor
+
+import android.app.AlarmManager.AlarmClockInfo
+import android.app.PendingIntent
+import android.content.Intent
+import android.provider.AlarmClock
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlarmTileUserActionInteractorTest : SysuiTestCase() {
+    private lateinit var activityStarter: ActivityStarter
+    private lateinit var intentCaptor: ArgumentCaptor<Intent>
+    private lateinit var pendingIntentCaptor: ArgumentCaptor<PendingIntent>
+
+    lateinit var underTest: AlarmTileUserActionInteractor
+
+    @Before
+    fun setup() {
+        activityStarter = mock<ActivityStarter>()
+        intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        pendingIntentCaptor = ArgumentCaptor.forClass(PendingIntent::class.java)
+        underTest = AlarmTileUserActionInteractor(activityStarter)
+    }
+
+    @Test
+    fun handleClickWithDefaultIntent() = runTest {
+        val alarmInfo = AlarmClockInfo(1L, null)
+        val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo)
+
+        underTest.handleInput(click(inputModel))
+
+        verify(activityStarter)
+            .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0), nullable())
+        assertThat(intentCaptor.value.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+    }
+
+    @Test
+    fun handleClickWithPendingIntent() = runTest {
+        val expectedIntent: PendingIntent = mock<PendingIntent>()
+        val alarmInfo = AlarmClockInfo(1L, expectedIntent)
+        val inputModel = AlarmTileModel.NextAlarmSet(true, alarmInfo)
+
+        underTest.handleInput(click(inputModel))
+
+        verify(activityStarter)
+            .postStartActivityDismissingKeyguard(capture(pendingIntentCaptor), nullable())
+        assertThat(pendingIntentCaptor.value).isEqualTo(expectedIntent)
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt
new file mode 100644
index 0000000..837857b
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/AlarmData.kt
@@ -0,0 +1,6 @@
+package com.android.systemui.plugins.clocks
+
+data class AlarmData(
+    val nextAlarmMillis: Long?,
+    val descriptionId: String?,
+)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
similarity index 97%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 63ded2e..1c5f221 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -11,7 +11,7 @@
  * KIND, either express or implied. See the License for the specific language governing
  * permissions and limitations under the License.
  */
-package com.android.systemui.plugins
+package com.android.systemui.plugins.clocks
 
 import android.content.res.Resources
 import android.graphics.Rect
@@ -20,6 +20,7 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import com.android.internal.annotations.Keep
 import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.Plugin
 import com.android.systemui.plugins.annotations.ProvidesInterface
 import java.io.PrintWriter
 import java.util.Locale
@@ -145,6 +146,12 @@
 
     /** Call whenever the weather data should update */
     fun onWeatherDataChanged(data: WeatherData)
+
+    /** Call with alarm information */
+    fun onAlarmDataChanged(data: AlarmData)
+
+    /** Call with zen/dnd information */
+    fun onZenDataChanged(data: ZenData)
 }
 
 /** Methods which trigger various clock animations */
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
similarity index 98%
rename from packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
rename to packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
index affb76b..789a473 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/WeatherData.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.plugins
+package com.android.systemui.plugins.clocks
 
 import android.os.Bundle
 import android.util.Log
@@ -7,8 +7,7 @@
 
 typealias WeatherTouchAction = (View) -> Unit
 
-class WeatherData
-constructor(
+data class WeatherData(
     val description: String,
     val state: WeatherStateIcon,
     val useCelsius: Boolean,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ZenData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ZenData.kt
new file mode 100644
index 0000000..e927ec3
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ZenData.kt
@@ -0,0 +1,22 @@
+package com.android.systemui.plugins.clocks
+
+import android.provider.Settings.Global.ZEN_MODE_ALARMS
+import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+import android.provider.Settings.Global.ZEN_MODE_OFF
+
+data class ZenData(
+    val zenMode: ZenMode,
+    val descriptionId: String?,
+) {
+    enum class ZenMode(val zenMode: Int) {
+        OFF(ZEN_MODE_OFF),
+        IMPORTANT_INTERRUPTIONS(ZEN_MODE_IMPORTANT_INTERRUPTIONS),
+        NO_INTERRUPTIONS(ZEN_MODE_NO_INTERRUPTIONS),
+        ALARMS(ZEN_MODE_ALARMS);
+
+        companion object {
+            fun fromInt(zenMode: Int) = values().firstOrNull { it.zenMode == zenMode }
+        }
+    }
+}
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7db21b2..b947801 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -125,6 +125,25 @@
     <dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
     <dimen name="navigation_edge_cancelled_edge_corners">6dp</dimen>
 
+    <!--
+         NOTICE: STATUS BAR INTERNALS. DO NOT READ THESE OUTSIDE OF STATUS BAR.
+
+         Below are the bottom margin values for each rotation [1].
+         Only used when the value is >= 0.
+         A value of 0 means that the content has 0 bottom margin, and will be at the bottom of the
+         status bar.
+         When the value is < 0, the value is ignored, and content will be centered vertically.
+
+         [1] Rotation defined as in android.view.Surface.Rotation.
+         Rotation 0 means natural orientation. If a device is naturally portrait (e.g. a phone),
+         rotation 0 is portrait. If a device is naturally landscape (e.g a tablet), rotation 0 is
+         landscape.
+     -->
+    <dimen name="status_bar_bottom_aligned_margin_rotation_0">-1px</dimen>
+    <dimen name="status_bar_bottom_aligned_margin_rotation_90">-1px</dimen>
+    <dimen name="status_bar_bottom_aligned_margin_rotation_180">-1px</dimen>
+    <dimen name="status_bar_bottom_aligned_margin_rotation_270">-1px</dimen>
+
     <!-- Height of the system icons container view in the status bar -->
     <dimen name="status_bar_system_icons_height">@dimen/status_bar_icon_size_sp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index c02ffa7..76abad8 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -33,8 +33,8 @@
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.customization.R
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.customization.R
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.DisplaySpecific
 import com.android.systemui.dagger.qualifiers.Main
@@ -49,14 +49,19 @@
 import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.KeyguardLargeClockLog
 import com.android.systemui.log.dagger.KeyguardSmallClockLog
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockFaceController
-import com.android.systemui.plugins.ClockTickRate
-import com.android.systemui.plugins.WeatherData
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockTickRate
+import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.res.R as SysuiR
 import com.android.systemui.shared.regionsampling.RegionSampler
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
@@ -88,7 +93,8 @@
     @Background private val bgExecutor: Executor,
     @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
     @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
-    private val featureFlags: FeatureFlags
+    private val featureFlags: FeatureFlags,
+    private val zenModeController: ZenModeController,
 ) {
     var clock: ClockController? = null
         set(value) {
@@ -137,12 +143,18 @@
                 }
                 updateFontSizes()
                 updateTimeListeners()
-                cachedWeatherData?.let {
+                weatherData?.let {
                     if (WeatherData.DEBUG) {
                         Log.i(TAG, "Pushing cached weather data to new clock: $it")
                     }
                     value.events.onWeatherDataChanged(it)
                 }
+                zenData?.let {
+                    value.events.onZenDataChanged(it)
+                }
+                alarmData?.let {
+                    value.events.onAlarmDataChanged(it)
+                }
 
                 smallClockOnAttachStateChangeListener =
                     object : OnAttachStateChangeListener {
@@ -260,7 +272,10 @@
     var largeTimeListener: TimeListener? = null
     val shouldTimeListenerRun: Boolean
         get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD
-    private var cachedWeatherData: WeatherData? = null
+
+    private var weatherData: WeatherData? = null
+    private var zenData: ZenData? = null
+    private var alarmData: AlarmData? = null
 
     private val configListener =
         object : ConfigurationController.ConfigurationListener {
@@ -321,14 +336,43 @@
 
             override fun onUserSwitchComplete(userId: Int) {
                 clock?.run { events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) }
+                zenModeCallback.onNextAlarmChanged()
             }
 
             override fun onWeatherDataChanged(data: WeatherData) {
-                cachedWeatherData = data
+                weatherData = data
                 clock?.run { events.onWeatherDataChanged(data) }
             }
         }
 
+    private val zenModeCallback = object : ZenModeController.Callback {
+        override fun onZenChanged(zen: Int) {
+            var mode = ZenMode.fromInt(zen)
+            if (mode == null) {
+                Log.e(TAG, "Failed to get zen mode from int: $zen")
+                return
+            }
+
+            zenData = ZenData(
+                mode,
+                if (mode == ZenMode.OFF) SysuiR.string::dnd_is_off.name
+                    else SysuiR.string::dnd_is_on.name
+            ).also { data ->
+                clock?.run { events.onZenDataChanged(data) }
+            }
+        }
+
+        override fun onNextAlarmChanged() {
+            val nextAlarmMillis = zenModeController.getNextAlarm()
+            alarmData = AlarmData(
+                if (nextAlarmMillis > 0) nextAlarmMillis else null,
+                SysuiR.string::status_bar_alarm.name
+            ).also { data ->
+                clock?.run { events.onAlarmDataChanged(data) }
+            }
+        }
+    }
+
     fun registerListeners(parent: View) {
         if (isRegistered) {
             return
@@ -341,6 +385,7 @@
         configurationController.addCallback(configListener)
         batteryController.addCallback(batteryCallback)
         keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+        zenModeController.addCallback(zenModeCallback)
         disposableHandle =
             parent.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -355,6 +400,10 @@
             }
         smallTimeListener?.update(shouldTimeListenerRun)
         largeTimeListener?.update(shouldTimeListenerRun)
+
+        // Query ZenMode data
+        zenModeCallback.onZenChanged(zenModeController.zen)
+        zenModeCallback.onNextAlarmChanged()
     }
 
     fun unregisterListeners() {
@@ -368,6 +417,7 @@
         configurationController.removeCallback(configListener)
         batteryController.removeCallback(batteryCallback)
         keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
+        zenModeController.removeCallback(zenModeCallback)
         smallRegionSampler?.stopRegionSampler()
         largeRegionSampler?.stopRegionSampler()
         smallTimeListener?.stop()
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 39a59c4..a5a545a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -24,7 +24,7 @@
 import com.android.keyguard.dagger.KeyguardStatusViewScope;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.LogLevel;
-import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.clocks.ClockController;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.clocks.DefaultClockController;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 54cb501..85c9fff 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -54,7 +54,7 @@
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.dagger.KeyguardClockLog;
-import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.clocks.ClockController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.clocks.ClockRegistry;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 4fbf077..2a54a4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -56,7 +56,7 @@
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.clocks.ClockController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.power.shared.model.ScreenPowerState;
 import com.android.systemui.res.R;
@@ -70,13 +70,13 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
 
+import kotlin.coroutines.CoroutineContext;
+import kotlin.coroutines.EmptyCoroutineContext;
+
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
 
-import kotlin.coroutines.CoroutineContext;
-import kotlin.coroutines.EmptyCoroutineContext;
-
 /**
  * Injectable controller for {@link KeyguardStatusView}.
  */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index c5bb099..37bd9b2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -131,7 +131,7 @@
 import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus;
 import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus;
 import com.android.systemui.log.SessionTracker;
-import com.android.systemui.plugins.WeatherData;
+import com.android.systemui.plugins.clocks.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 2476067..02dd331 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -23,7 +23,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.plugins.WeatherData;
+import com.android.systemui.plugins.clocks.WeatherData;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.util.annotations.WeaklyReferencedCallback;
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 1edb551..3cb6314 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -34,8 +34,8 @@
 import android.view.SurfaceControl;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnection;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -55,7 +55,7 @@
 /**
  * Class to handle the interaction with
  * {@link com.android.server.accessibility.AccessibilityManagerService}. It invokes
- * {@link AccessibilityManager#setWindowMagnificationConnection(IWindowMagnificationConnection)}
+ * {@link AccessibilityManager#setMagnificationConnection(IMagnificationConnection)}
  * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
  */
 @SysUISingleton
@@ -484,11 +484,11 @@
     }
 
     @Override
-    public void requestWindowMagnificationConnection(boolean connect) {
+    public void requestMagnificationConnection(boolean connect) {
         if (connect) {
-            setWindowMagnificationConnection();
+            setMagnificationConnection();
         } else {
-            clearWindowMagnificationConnection();
+            clearMagnificationConnection();
         }
     }
 
@@ -499,17 +499,17 @@
                 magnificationController -> magnificationController.dump(pw));
     }
 
-    private void setWindowMagnificationConnection() {
+    private void setMagnificationConnection() {
         if (mMagnificationConnectionImpl == null) {
             mMagnificationConnectionImpl = new MagnificationConnectionImpl(this,
                     mHandler);
         }
-        mAccessibilityManager.setWindowMagnificationConnection(
+        mAccessibilityManager.setMagnificationConnection(
                 mMagnificationConnectionImpl);
     }
 
-    private void clearWindowMagnificationConnection() {
-        mAccessibilityManager.setWindowMagnificationConnection(null);
+    private void clearMagnificationConnection() {
+        mAccessibilityManager.setMagnificationConnection(null);
         //TODO: destroy controllers.
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
index 5f0d496..4944531 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationConnectionImpl.java
@@ -21,8 +21,8 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 
 import com.android.systemui.dagger.qualifiers.Main;
@@ -30,9 +30,9 @@
 /**
  * Implementation of window magnification connection.
  *
- * @see IWindowMagnificationConnection
+ * @see IMagnificationConnection
  */
-class MagnificationConnectionImpl extends IWindowMagnificationConnection.Stub {
+class MagnificationConnectionImpl extends IMagnificationConnection.Stub {
 
     private static final String TAG = "WindowMagnificationConnectionImpl";
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
rename to packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
index 2d2f2956..91bc0c1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt
@@ -42,18 +42,21 @@
 import com.android.systemui.util.settings.SystemSettings
 import com.android.systemui.util.time.SystemClock
 import java.util.concurrent.atomic.AtomicInteger
+import javax.inject.Inject
 import kotlin.math.roundToInt
 
 /** The Dialog that contains a seekbar for changing the font size. */
-class FontScalingDialog(
-    context: Context,
+class FontScalingDialogDelegate @Inject constructor(
+    private val context: Context,
+    private val systemUIDialogFactory: SystemUIDialog.Factory,
+    private val layoutInflater: LayoutInflater,
     private val systemSettings: SystemSettings,
     private val secureSettings: SecureSettings,
     private val systemClock: SystemClock,
     private val userTracker: UserTracker,
     @Main mainHandler: Handler,
-    @Background private val backgroundDelayableExecutor: DelayableExecutor
-) : SystemUIDialog(context) {
+    @Background private val backgroundDelayableExecutor: DelayableExecutor,
+) : SystemUIDialog.Delegate {
     private val MIN_UPDATE_INTERVAL_MS: Long = 800
     private val CHANGE_BY_SEEKBAR_DELAY_MS: Long = 100
     private val CHANGE_BY_BUTTON_DELAY_MS: Long = 300
@@ -75,19 +78,22 @@
             }
         }
 
-    override fun onCreate(savedInstanceState: Bundle?) {
-        setTitle(R.string.font_scaling_dialog_title)
-        setView(LayoutInflater.from(context).inflate(R.layout.font_scaling_dialog, null))
-        setPositiveButton(
-            R.string.quick_settings_done,
-            /* onClick = */ null,
-            /* dismissOnClick = */ true
-        )
-        super.onCreate(savedInstanceState)
+    override fun createDialog(): SystemUIDialog = systemUIDialogFactory.create(this)
 
-        title = requireViewById(com.android.internal.R.id.alertTitle)
-        doneButton = requireViewById(com.android.internal.R.id.button1)
-        seekBarWithIconButtonsView = requireViewById(R.id.font_scaling_slider)
+    override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        dialog.setTitle(R.string.font_scaling_dialog_title)
+        dialog.setView(layoutInflater.inflate(R.layout.font_scaling_dialog, null))
+        dialog.setPositiveButton(
+                R.string.quick_settings_done,
+                /* onClick = */ null,
+                /* dismissOnClick = */ true
+        )
+    }
+
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        title = dialog.requireViewById(com.android.internal.R.id.alertTitle)
+        doneButton = dialog.requireViewById(com.android.internal.R.id.button1)
+        seekBarWithIconButtonsView = dialog.requireViewById(R.id.font_scaling_slider)
 
         val labelArray = arrayOfNulls<String>(strEntryValues.size)
         for (i in strEntryValues.indices) {
@@ -135,7 +141,7 @@
                 }
             }
         )
-        doneButton.setOnClickListener { dismiss() }
+        doneButton.setOnClickListener { dialog.dismiss() }
         systemSettings.registerContentObserver(Settings.System.FONT_SCALE, fontSizeObserver)
     }
 
@@ -156,7 +162,7 @@
             backgroundDelayableExecutor.executeDelayed({ updateFontScale() }, delayMs)
     }
 
-    override fun stop() {
+    override fun onStop(dialog: SystemUIDialog) {
         cancelUpdateFontScaleRunnable?.run()
         cancelUpdateFontScaleRunnable = null
         systemSettings.unregisterContentObserver(fontSizeObserver)
@@ -189,9 +195,7 @@
         return strEntryValues.size - 1
     }
 
-    override fun onConfigurationChanged(configuration: Configuration) {
-        super.onConfigurationChanged(configuration)
-
+    override fun onConfigurationChanged(dialog: SystemUIDialog, configuration: Configuration) {
         val configDiff = configuration.diff(this.configuration)
         this.configuration.setTo(configuration)
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 70736ae..bfc80a7 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -61,12 +61,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
 import com.android.systemui.screenshot.TimeoutHandler;
 
 import java.util.Optional;
@@ -297,6 +297,7 @@
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED);
                     mIsMinimized = true;
                     mView.setMinimized(true);
+                    animateIn();
                 } else {
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
                     setExpandedView(this::animateIn);
@@ -318,8 +319,8 @@
                 } else {
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_SHOWN_EXPANDED);
                     setExpandedView();
-                    animateIn();
                 }
+                animateIn();
                 mView.announceForAccessibility(
                         getAccessibilityAnnouncement(mClipboardModel.getType()));
             } else if (!mIsMinimized) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
index ad02f62..4219d6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt
@@ -2,63 +2,16 @@
 
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.view.layout.sections.removeView
-import com.android.systemui.res.R
 import javax.inject.Inject
 
 /** A keyguard section that hosts the communal hub. */
-class DefaultCommunalHubSection
-@Inject
-constructor(
-    private val viewModel: CommunalViewModel,
-) : KeyguardSection() {
-    private val communalHubViewId = R.id.communal_hub
-
-    override fun addViews(constraintLayout: ConstraintLayout) {
-        constraintLayout.addView(
-            ComposeFacade.createCommunalView(
-                    context = constraintLayout.context,
-                    viewModel = viewModel,
-                )
-                .apply { id = communalHubViewId },
-        )
-    }
+class DefaultCommunalHubSection @Inject constructor() : KeyguardSection() {
+    override fun addViews(constraintLayout: ConstraintLayout) {}
 
     override fun bindData(constraintLayout: ConstraintLayout) {}
 
-    override fun applyConstraints(constraintSet: ConstraintSet) {
-        constraintSet.apply {
-            connect(
-                communalHubViewId,
-                ConstraintSet.START,
-                ConstraintSet.PARENT_ID,
-                ConstraintSet.START,
-            )
-            connect(
-                communalHubViewId,
-                ConstraintSet.TOP,
-                ConstraintSet.PARENT_ID,
-                ConstraintSet.TOP,
-            )
-            connect(
-                communalHubViewId,
-                ConstraintSet.END,
-                ConstraintSet.PARENT_ID,
-                ConstraintSet.END,
-            )
-            connect(
-                communalHubViewId,
-                ConstraintSet.BOTTOM,
-                ConstraintSet.PARENT_ID,
-                ConstraintSet.BOTTOM,
-            )
-        }
-    }
+    override fun applyConstraints(constraintSet: ConstraintSet) {}
 
-    override fun removeViews(constraintLayout: ConstraintLayout) {
-        constraintLayout.removeView(communalHubViewId)
-    }
+    override fun removeViews(constraintLayout: ConstraintLayout) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 4d8e893..bed4283 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -16,16 +16,22 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.os.PowerManager
+import android.os.SystemClock
+import android.view.MotionEvent
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.shade.ShadeViewController
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
 /** The base view model for the communal hub. */
 abstract class BaseCommunalViewModel(
     private val communalInteractor: CommunalInteractor,
+    private val shadeViewController: ShadeViewController,
+    private val powerManager: PowerManager,
     val mediaHost: MediaHost,
 ) {
     val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
@@ -36,6 +42,26 @@
         communalInteractor.onSceneChanged(scene)
     }
 
+    // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
+    //  touches anymore.
+    /** Called when a touch is received outside the edge swipe area when hub mode is closed. */
+    fun onOuterTouch(motionEvent: MotionEvent) {
+        // Forward the touch to the shade so that basic gestures like swipe up/down for
+        // shade/bouncer work.
+        shadeViewController.handleExternalTouch(motionEvent)
+    }
+
+    // TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
+    //  touches anymore.
+    /** Called to refresh the screen timeout when a user touch is received. */
+    fun onUserActivity() {
+        powerManager.userActivity(
+            SystemClock.uptimeMillis(),
+            PowerManager.USER_ACTIVITY_EVENT_TOUCH,
+            0
+        )
+    }
+
     /** A list of all the communal content to be displayed in the communal hub. */
     abstract val communalContent: Flow<List<CommunalContentModel>>
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 111f8b4..b6843c5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.os.PowerManager
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
+import com.android.systemui.shade.ShadeViewController
 import javax.inject.Inject
 import javax.inject.Named
 import kotlinx.coroutines.flow.Flow
@@ -31,8 +33,10 @@
 @Inject
 constructor(
     private val communalInteractor: CommunalInteractor,
+    shadeViewController: ShadeViewController,
+    powerManager: PowerManager,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
-) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
 
     override val isEditMode = true
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 11bde6b..d7dcdb9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,12 +16,14 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
+import android.os.PowerManager
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
+import com.android.systemui.shade.ShadeViewController
 import javax.inject.Inject
 import javax.inject.Named
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,8 +39,10 @@
 constructor(
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
+    shadeViewController: ShadeViewController,
+    powerManager: PowerManager,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
-) : BaseCommunalViewModel(communalInteractor, mediaHost) {
+) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
     @OptIn(ExperimentalCoroutinesApi::class)
     override val communalContent: Flow<List<CommunalContentModel>> =
         tutorialInteractor.isTutorialAvailable.flatMapLatest { isTutorialMode ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 8d5e6c3..5e3779a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -26,8 +26,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 3887e69..356c408 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -22,8 +22,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockId
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 48f6092..b1c40b5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 0addbd8..ebc9c5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -47,7 +47,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.CrossFadeHelper
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 11872d9..4eecfde 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -27,6 +27,8 @@
 import android.os.Bundle
 import android.os.Handler
 import android.os.IBinder
+import android.provider.Settings
+import android.util.Log
 import android.view.ContextThemeWrapper
 import android.view.Display
 import android.view.Display.DEFAULT_DISPLAY
@@ -47,6 +49,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
@@ -64,8 +67,9 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.monet.ColorScheme
-import com.android.systemui.plugins.ClockController
+import com.android.systemui.monet.Style
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.clocks.ClockRegistry
@@ -79,13 +83,21 @@
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.util.settings.SecureSettings
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import org.json.JSONException
+import org.json.JSONObject
 
 /** Renders the preview of the lock screen. */
 class KeyguardPreviewRenderer
@@ -93,8 +105,10 @@
 @AssistedInject
 constructor(
     @Application private val context: Context,
+    @Application applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Main private val mainHandler: Handler,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val clockViewModel: KeyguardPreviewClockViewModel,
     private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
     private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
@@ -118,6 +132,7 @@
     private val chipbarCoordinator: ChipbarCoordinator,
     private val screenOffAnimationController: ScreenOffAnimationController,
     private val shadeInteractor: ShadeInteractor,
+    private val secureSettings: SecureSettings,
 ) {
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
     private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -156,7 +171,13 @@
 
     private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
 
+    private val coroutineScope: CoroutineScope
+    private var themeStyle: Style? = null
+
     init {
+        coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
+        disposables.add(DisposableHandle { coroutineScope.cancel() })
+
         if (keyguardBottomAreaRefactor()) {
             quickAffordancesCombinedViewModel.enablePreviewMode(
                 initiallySelectedSlotId =
@@ -553,29 +574,54 @@
     }
 
     private fun onClockChanged() {
-        val clock = clockRegistry.createCurrentClock()
-        clockController.clock = clock
+        coroutineScope.launch {
+            val clock = clockRegistry.createCurrentClock()
+            clockController.clock = clock
 
-        if (clockRegistry.seedColor == null) {
-            // Seed color null means users do override any color on the clock. The default color
-            // will need to use wallpaper's extracted color and consider if the wallpaper's color
-            // is dark or a light.
-            // TODO(b/277832214) we can potentially simplify this code by checking for
-            // wallpaperColors being null in the if clause above and removing the many ?.
-            val wallpaperColorScheme = wallpaperColors?.let { ColorScheme(it, darkTheme = false) }
-            val lightClockColor = wallpaperColorScheme?.accent1?.s100
-            val darkClockColor = wallpaperColorScheme?.accent2?.s600
+            val colors = wallpaperColors
+            if (clockRegistry.seedColor == null && colors != null) {
+                // Seed color null means users do not override any color on the clock. The default
+                // color will need to use wallpaper's extracted color and consider if the
+                // wallpaper's color is dark or light.
+                val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
+                val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
+                val lightClockColor = wallpaperColorScheme.accent1.s100
+                val darkClockColor = wallpaperColorScheme.accent2.s600
 
-            // Note that when [wallpaperColors] is null, isWallpaperDark is true.
-            val isWallpaperDark: Boolean =
-                (wallpaperColors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
-            clock.events.onSeedColorChanged(
-                if (isWallpaperDark) lightClockColor else darkClockColor
-            )
+                // Note that when [wallpaperColors] is null, isWallpaperDark is true.
+                val isWallpaperDark: Boolean =
+                    (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
+                clock.events.onSeedColorChanged(
+                    if (isWallpaperDark) lightClockColor else darkClockColor
+                )
+            }
+
+            updateLargeClock(clock)
+            updateSmallClock(clock)
         }
+    }
 
-        updateLargeClock(clock)
-        updateSmallClock(clock)
+    private suspend fun fetchThemeStyleFromSetting(): Style {
+        val overlayPackageJson =
+            withContext(backgroundDispatcher) {
+                secureSettings.getString(
+                    Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                )
+            }
+        return if (!overlayPackageJson.isNullOrEmpty()) {
+            try {
+                val jsonObject = JSONObject(overlayPackageJson)
+                Style.valueOf(jsonObject.getString(OVERLAY_CATEGORY_THEME_STYLE))
+            } catch (e: (JSONException)) {
+                Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e)
+                Style.TONAL_SPOT
+            } catch (e: IllegalArgumentException) {
+                Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e)
+                Style.TONAL_SPOT
+            }
+        } else {
+            Style.TONAL_SPOT
+        }
     }
 
     private fun updateLargeClock(clock: ClockController) {
@@ -601,6 +647,8 @@
     }
 
     companion object {
+        private const val TAG = "KeyguardPreviewRenderer"
+        private const val OVERLAY_CATEGORY_THEME_STYLE = "android.theme.customization.theme_style"
         private const val KEY_HOST_TOKEN = "host_token"
         private const val KEY_VIEW_WIDTH = "width"
         private const val KEY_VIEW_HEIGHT = "height"
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 021f064..c8b2d39 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
@@ -33,8 +33,8 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockFaceLayout
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFaceLayout
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.Utils
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 7ffa149..3aeff61 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
@@ -24,7 +24,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
-import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.clocks.ClockController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 889464d..d250c1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -36,7 +36,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 64e3f16..14d3658 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -23,7 +23,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.res.R
-import com.android.systemui.accessibility.fontscaling.FontScalingDialog
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.dagger.qualifiers.Background
@@ -36,14 +36,10 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SystemSettings
-import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
+import javax.inject.Provider
 
 class FontScalingTile
 @Inject
@@ -59,11 +55,7 @@
     qsLogger: QSLogger,
     private val keyguardStateController: KeyguardStateController,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val systemSettings: SystemSettings,
-    private val secureSettings: SecureSettings,
-    private val systemClock: SystemClock,
-    private val userTracker: UserTracker,
-    @Background private val backgroundDelayableExecutor: DelayableExecutor
+    private val fontScalingDialogDelegateProvider: Provider<FontScalingDialogDelegate>
 ) :
     QSTileImpl<QSTile.State?>(
         host,
@@ -87,16 +79,7 @@
         val animateFromView: Boolean = view != null && !keyguardStateController.isShowing
 
         val runnable = Runnable {
-            val dialog: SystemUIDialog =
-                FontScalingDialog(
-                    mContext,
-                    systemSettings,
-                    secureSettings,
-                    systemClock,
-                    userTracker,
-                    mainHandler,
-                    backgroundDelayableExecutor
-                )
+            val dialog: SystemUIDialog = fontScalingDialogDelegateProvider.get().createDialog()
             if (animateFromView) {
                 dialogLaunchAnimator.showFromView(
                     dialog,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
new file mode 100644
index 0000000..6386577
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.alarm.domain
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import java.util.TimeZone
+import javax.inject.Inject
+
+/** Maps [AlarmTileModel] to [QSTileState]. */
+class AlarmTileMapper @Inject constructor(@Main private val resources: Resources) :
+    QSTileDataToStateMapper<AlarmTileModel> {
+    companion object {
+        val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a")
+        val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
+    }
+    override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
+        QSTileState.build(resources, config.uiConfig) {
+            when (data) {
+                is AlarmTileModel.NextAlarmSet -> {
+                    activationState = QSTileState.ActivationState.ACTIVE
+
+                    val localDateTime =
+                        LocalDateTime.ofInstant(
+                            Instant.ofEpochMilli(data.alarmClockInfo.triggerTime),
+                            TimeZone.getDefault().toZoneId()
+                        )
+                    secondaryLabel =
+                        if (data.is24HourFormat) formatter24Hour.format(localDateTime)
+                        else formatter12Hour.format(localDateTime)
+                }
+                is AlarmTileModel.NoAlarmSet -> {
+                    activationState = QSTileState.ActivationState.INACTIVE
+                    secondaryLabel = resources.getString(R.string.qs_alarm_tile_no_alarm)
+                }
+            }
+
+            contentDescription = label
+            supportedActions = setOf(QSTileState.UserAction.CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractor.kt
new file mode 100644
index 0000000..51cd501
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileDataInteractor.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.alarm.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
+import com.android.systemui.statusbar.policy.NextAlarmController
+import com.android.systemui.util.time.DateFormatUtil
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Observes alarm state changes providing the [AlarmTileModel]. */
+class AlarmTileDataInteractor
+@Inject
+constructor(
+    private val alarmController: NextAlarmController,
+    private val dateFormatUtil: DateFormatUtil
+) : QSTileDataInteractor<AlarmTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<AlarmTileModel> =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val alarmCallback =
+                NextAlarmController.NextAlarmChangeCallback {
+                    val model =
+                        if (it == null) AlarmTileModel.NoAlarmSet
+                        else AlarmTileModel.NextAlarmSet(dateFormatUtil.is24HourFormat, it)
+                    trySend(model)
+                }
+            alarmController.addCallback(alarmCallback)
+
+            awaitClose { alarmController.removeCallback(alarmCallback) }
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
new file mode 100644
index 0000000..afca57c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.alarm.domain.interactor
+
+import android.content.Intent
+import android.provider.AlarmClock
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles alarm tile clicks. */
+class AlarmTileUserActionInteractor
+@Inject
+constructor(
+    private val activityStarter: ActivityStarter,
+) : QSTileUserActionInteractor<AlarmTileModel> {
+    override suspend fun handleInput(input: QSTileInput<AlarmTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    val animationController =
+                        action.view?.let {
+                            ActivityLaunchAnimator.Controller.fromView(
+                                it,
+                                InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+                            )
+                        }
+                    if (
+                        data is AlarmTileModel.NextAlarmSet &&
+                            data.alarmClockInfo.showIntent != null
+                    ) {
+                        val pendingIndent = data.alarmClockInfo.showIntent
+                        activityStarter.postStartActivityDismissingKeyguard(
+                            pendingIndent,
+                            animationController
+                        )
+                    } else {
+                        activityStarter.postStartActivityDismissingKeyguard(
+                            Intent(AlarmClock.ACTION_SHOW_ALARMS),
+                            0,
+                            animationController
+                        )
+                    }
+                }
+                is QSTileUserAction.LongClick -> {}
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/model/AlarmTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/model/AlarmTileModel.kt
new file mode 100644
index 0000000..7647d7c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/model/AlarmTileModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.alarm.domain.model
+
+import android.app.AlarmManager
+
+/** Alarm tile model */
+sealed interface AlarmTileModel {
+    data object NoAlarmSet : AlarmTileModel
+    data class NextAlarmSet(
+        val is24HourFormat: Boolean,
+        val alarmClockInfo: AlarmManager.AlarmClockInfo
+    ) : AlarmTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 61ff9a5..1e86b11 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -61,7 +61,6 @@
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.PowerManager;
 import android.os.Trace;
 import android.os.UserManager;
 import android.os.VibrationEffect;
@@ -165,6 +164,7 @@
 import com.android.systemui.power.shared.model.WakefulnessModel;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
@@ -353,6 +353,7 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
     private final ShadeRepository mShadeRepository;
+    private final ShadeAnimationInteractor mShadeAnimationInteractor;
     private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
     private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
     private final NotificationGutsManager mGutsManager;
@@ -363,7 +364,6 @@
 
     private long mDownTime;
     private boolean mTouchSlopExceededBeforeDown;
-    private boolean mIsLaunchAnimationRunning;
     private float mOverExpansion;
     private CentralSurfaces mCentralSurfaces;
     private HeadsUpManager mHeadsUpManager;
@@ -563,7 +563,6 @@
     private boolean mHasLayoutedSinceDown;
     private float mUpdateFlingVelocity;
     private boolean mUpdateFlingOnLayout;
-    private boolean mClosing;
     private boolean mTouchSlopExceeded;
     private int mTrackingPointer;
     private int mTouchSlop;
@@ -708,7 +707,6 @@
             CommandQueue commandQueue,
             VibratorHelper vibratorHelper,
             LatencyTracker latencyTracker,
-            PowerManager powerManager,
             AccessibilityManager accessibilityManager,
             @DisplayId int displayId,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -778,6 +776,7 @@
             ActivityStarter activityStarter,
             SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
             ActiveNotificationsInteractor activeNotificationsInteractor,
+            ShadeAnimationInteractor shadeAnimationInteractor,
             KeyguardViewConfigurator keyguardViewConfigurator,
             KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
             SplitShadeStateController splitShadeStateController,
@@ -796,6 +795,7 @@
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mShadeRepository = shadeRepository;
+        mShadeAnimationInteractor = shadeAnimationInteractor;
         mShadeLog = shadeLogger;
         mGutsManager = gutsManager;
         mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
@@ -2926,21 +2926,13 @@
         }
     }
 
-    @Override
-    public void setIsLaunchAnimationRunning(boolean running) {
-        boolean wasRunning = mIsLaunchAnimationRunning;
-        mIsLaunchAnimationRunning = running;
-        if (wasRunning != mIsLaunchAnimationRunning) {
-            mShadeExpansionStateManager.notifyLaunchingActivityChanged(running);
-        }
+    private boolean isLaunchingActivity() {
+        return mShadeAnimationInteractor.isLaunchingActivity().getValue();
     }
 
     @VisibleForTesting
     void setClosing(boolean isClosing) {
-        if (mClosing != isClosing) {
-            mClosing = isClosing;
-            mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing);
-        }
+        mShadeRepository.setLegacyIsClosing(isClosing);
         mAmbientState.setIsClosing(isClosing);
     }
 
@@ -3123,7 +3115,7 @@
 
     @Override
     public boolean shouldHideStatusBarIconsWhenExpanded() {
-        if (mIsLaunchAnimationRunning) {
+        if (isLaunchingActivity()) {
             return mHideIconsDuringLaunchAnimation;
         }
         if (mHeadsUpAppearanceController != null
@@ -3389,7 +3381,7 @@
 
         ipw.print("mDownTime="); ipw.println(mDownTime);
         ipw.print("mTouchSlopExceededBeforeDown="); ipw.println(mTouchSlopExceededBeforeDown);
-        ipw.print("mIsLaunchAnimationRunning="); ipw.println(mIsLaunchAnimationRunning);
+        ipw.print("mIsLaunchAnimationRunning="); ipw.println(isLaunchingActivity());
         ipw.print("mOverExpansion="); ipw.println(mOverExpansion);
         ipw.print("mExpandedHeight="); ipw.println(mExpandedHeight);
         ipw.print("isTracking()="); ipw.println(isTracking());
@@ -3471,7 +3463,7 @@
         ipw.print("mHasLayoutedSinceDown="); ipw.println(mHasLayoutedSinceDown);
         ipw.print("mUpdateFlingVelocity="); ipw.println(mUpdateFlingVelocity);
         ipw.print("mUpdateFlingOnLayout="); ipw.println(mUpdateFlingOnLayout);
-        ipw.print("mClosing="); ipw.println(mClosing);
+        ipw.print("isClosing()="); ipw.println(isClosing());
         ipw.print("mTouchSlopExceeded="); ipw.println(mTouchSlopExceeded);
         ipw.print("mTrackingPointer="); ipw.println(mTrackingPointer);
         ipw.print("mTouchSlop="); ipw.println(mTouchSlop);
@@ -3810,7 +3802,7 @@
     }
 
     private void endClosing() {
-        if (mClosing) {
+        if (isClosing()) {
             setClosing(false);
             onClosingFinished();
         }
@@ -3930,7 +3922,7 @@
             mExpandedHeight = Math.min(h, maxPanelHeight);
             // If we are closing the panel and we are almost there due to a slow decelerating
             // interpolator, abort the animation.
-            if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
+            if (mExpandedHeight < 1f && mExpandedHeight != 0f && isClosing()) {
                 mExpandedHeight = 0f;
                 if (mHeightAnimator != null) {
                     mHeightAnimator.end();
@@ -4005,7 +3997,7 @@
 
     @Override
     public boolean isCollapsing() {
-        return mClosing || mIsLaunchAnimationRunning;
+        return isClosing() || isLaunchingActivity();
     }
 
     public boolean isTracking() {
@@ -4014,7 +4006,7 @@
 
     @Override
     public boolean canBeCollapsed() {
-        return !isFullyCollapsed() && !isTracking() && !mClosing;
+        return !isFullyCollapsed() && !isTracking() && !isClosing();
     }
 
     @Override
@@ -4129,7 +4121,7 @@
 
     @VisibleForTesting
     boolean isClosing() {
-        return mClosing;
+        return mShadeRepository.getLegacyIsClosing().getValue();
     }
 
     @Override
@@ -4842,11 +4834,11 @@
                     mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
                     mMinExpandHeight = 0.0f;
                     mDownTime = mSystemClock.uptimeMillis();
-                    if (mAnimatingOnDown && mClosing) {
+                    if (mAnimatingOnDown && isClosing()) {
                         cancelHeightAnimator();
                         mTouchSlopExceeded = true;
                         mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:"
-                                + " mAnimatingOnDown: true, mClosing: true");
+                                + " mAnimatingOnDown: true, isClosing(): true");
                         return true;
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 53eccfd..67bb814 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -17,6 +17,10 @@
 package com.android.systemui.shade
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl
 import dagger.Binds
@@ -36,4 +40,14 @@
     @Binds
     @SysUISingleton
     abstract fun bindsShadeInteractor(si: ShadeInteractorEmptyImpl): ShadeInteractor
+
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeRepository(impl: ShadeRepositoryImpl): ShadeRepository
+
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeAnimationInteractor(
+        sai: ShadeAnimationInteractorEmptyImpl
+    ): ShadeAnimationInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
deleted file mode 100644
index f87a1ed..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade;
-
-import com.android.systemui.shade.data.repository.ShadeRepository;
-import com.android.systemui.shade.data.repository.ShadeRepositoryImpl;
-
-import dagger.Binds;
-import dagger.Module;
-
-/** Provides Shade-related events and information. */
-@Module
-public abstract class ShadeEventsModule {
-    @Binds
-    abstract ShadeStateEvents bindShadeEvents(ShadeExpansionStateManager impl);
-
-    @Binds abstract ShadeRepository shadeRepository(ShadeRepositoryImpl impl);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index e20534c..8a93ef6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -22,7 +22,6 @@
 import android.util.Log
 import androidx.annotation.FloatRange
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
 import com.android.systemui.util.Compile
 import java.util.concurrent.CopyOnWriteArrayList
 import javax.inject.Inject
@@ -33,11 +32,10 @@
  * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
  */
 @SysUISingleton
-class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
+class ShadeExpansionStateManager @Inject constructor() {
 
     private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
     private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
-    private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
 
     @PanelState private var state: Int = STATE_CLOSED
     @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
@@ -66,14 +64,6 @@
         stateListeners.add(listener)
     }
 
-    override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) {
-        shadeStateEventsListeners.addIfAbsent(listener)
-    }
-
-    override fun removeShadeStateEventsListener(listener: ShadeStateEventsListener) {
-        shadeStateEventsListeners.remove(listener)
-    }
-
     /** Returns true if the panel is currently closed and false otherwise. */
     fun isClosed(): Boolean = state == STATE_CLOSED
 
@@ -157,18 +147,6 @@
         stateListeners.forEach { it.onPanelStateChanged(state) }
     }
 
-    fun notifyLaunchingActivityChanged(isLaunchingActivity: Boolean) {
-        for (cb in shadeStateEventsListeners) {
-            cb.onLaunchingActivityChanged(isLaunchingActivity)
-        }
-    }
-
-    fun notifyPanelCollapsingChanged(isCollapsing: Boolean) {
-        for (cb in shadeStateEventsListeners) {
-            cb.onPanelCollapsingChanged(isCollapsing)
-        }
-    }
-
     private fun debugLog(msg: String) {
         if (!DEBUG) return
         Log.v(TAG, msg)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index cb95b25..2460a33 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -23,11 +23,11 @@
 import android.app.StatusBarManager
 import android.content.Intent
 import android.content.res.Configuration
+import android.graphics.Insets
 import android.os.Bundle
 import android.os.Trace
 import android.os.Trace.TRACE_TAG_APP
 import android.provider.AlarmClock
-import android.util.Pair
 import android.view.DisplayCutout
 import android.view.View
 import android.view.WindowInsets
@@ -402,9 +402,9 @@
     private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
         val cutout = insets.displayCutout.also { this.cutout = it }
 
-        val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
-        val cutoutLeft = sbInsets.first
-        val cutoutRight = sbInsets.second
+        val sbInsets: Insets = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
+        val cutoutLeft = sbInsets.left
+        val cutoutRight = sbInsets.right
         val hasCornerCutout: Boolean = insetsProvider.currentRotationHasCornerCutout()
         updateQQSPaddings()
         // Set these guides as the left/right limits for content that lives in the top row, using
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 54467cf..c057147 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -18,7 +18,12 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
 import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
 import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
@@ -45,10 +50,28 @@
                 sceneContainerOff.get()
             }
         }
+
+        @Provides
+        @SysUISingleton
+        fun provideShadeAnimationInteractor(
+            sceneContainerFlags: SceneContainerFlags,
+            sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
+            sceneContainerOff: Provider<ShadeAnimationInteractorLegacyImpl>
+        ): ShadeAnimationInteractor {
+            return if (sceneContainerFlags.isEnabled()) {
+                sceneContainerOn.get()
+            } else {
+                sceneContainerOff.get()
+            }
+        }
     }
 
     @Binds
     @SysUISingleton
+    abstract fun bindsShadeRepository(impl: ShadeRepositoryImpl): ShadeRepository
+
+    @Binds
+    @SysUISingleton
     abstract fun bindsShadeInteractor(si: ShadeInteractorImpl): ShadeInteractor
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
deleted file mode 100644
index c8511d7..0000000
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shade
-
-/** Provides certain notification panel events. */
-interface ShadeStateEvents {
-
-    /** Registers callbacks to be invoked when notification panel events occur. */
-    fun addShadeStateEventsListener(listener: ShadeStateEventsListener)
-
-    /** Unregisters callbacks previously registered via [addShadeStateEventsListener] */
-    fun removeShadeStateEventsListener(listener: ShadeStateEventsListener)
-
-    /** Callbacks for certain notification panel events. */
-    interface ShadeStateEventsListener {
-
-        /** Invoked when the notification panel starts or stops collapsing. */
-        fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
-
-        /**
-         * Invoked when the notification panel starts or stops launching an [android.app.Activity].
-         */
-        fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 637cf96..3430eed 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -158,9 +158,6 @@
     /** Sets progress of the predictive back animation. */
     fun onBackProgressed(progressFraction: Float)
 
-    /** Sets whether the status bar launch animation is currently running. */
-    fun setIsLaunchAnimationRunning(running: Boolean)
-
     /** Sets the alpha value of the shade to a value between 0 and 255. */
     fun setAlpha(alpha: Int, animate: Boolean)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 2ed62dd..1240c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -59,7 +59,6 @@
     }
     override fun onBackPressed() {}
     override fun onBackProgressed(progressFraction: Float) {}
-    override fun setIsLaunchAnimationRunning(running: Boolean) {}
     override fun setAlpha(alpha: Int, animate: Boolean) {}
     override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
     override fun setPulsing(pulsing: Boolean) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeAnimationRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeAnimationRepository.kt
new file mode 100644
index 0000000..b99a170
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeAnimationRepository.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Data related to programmatic shade animations. */
+@SysUISingleton
+class ShadeAnimationRepository @Inject constructor() {
+    val isLaunchingActivity = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 47b08fe..e94a3eb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -105,6 +105,12 @@
     /** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */
     @Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean>
 
+    /** NPVC.mClosing as a flow. */
+    @Deprecated("Use ShadeAnimationInteractor instead") val legacyIsClosing: StateFlow<Boolean>
+
+    /** Sets whether a closing animation is happening. */
+    @Deprecated("Use ShadeAnimationInteractor instead") fun setLegacyIsClosing(isClosing: Boolean)
+
     /**  */
     @Deprecated("Use ShadeInteractor instead")
     fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean)
@@ -261,6 +267,15 @@
         _legacyShadeTracking.value = tracking
     }
 
+    private val _legacyIsClosing = MutableStateFlow(false)
+    @Deprecated("Use ShadeInteractor instead")
+    override val legacyIsClosing: StateFlow<Boolean> = _legacyIsClosing.asStateFlow()
+
+    @Deprecated("Use ShadeInteractor instead")
+    override fun setLegacyIsClosing(isClosing: Boolean) {
+        _legacyIsClosing.value = isClosing
+    }
+
     @Deprecated("Should only be called by NPVC and tests")
     override fun setLegacyLockscreenShadeTracking(tracking: Boolean) {
         legacyLockscreenShadeTracking.value = tracking
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
new file mode 100644
index 0000000..5a777e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Business logic related to shade animations and transitions. */
+abstract class ShadeAnimationInteractor(
+    private val shadeAnimationRepository: ShadeAnimationRepository,
+) {
+    val isLaunchingActivity: StateFlow<Boolean> =
+        shadeAnimationRepository.isLaunchingActivity.asStateFlow()
+
+    fun setIsLaunchingActivity(launching: Boolean) {
+        shadeAnimationRepository.isLaunchingActivity.value = launching
+    }
+
+    /**
+     * Whether a short animation to close the shade or QS is running. This will be false if the user
+     * is manually closing the shade or QS but true if they lift their finger and an animation
+     * completes the close. Important: if QS is collapsing back to shade, this will be false because
+     * that is not considered "closing".
+     */
+    abstract val isAnyCloseAnimationRunning: Flow<Boolean>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
new file mode 100644
index 0000000..2a7658a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.flowOf
+
+/** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */
+@SysUISingleton
+class ShadeAnimationInteractorEmptyImpl
+@Inject
+constructor(
+    shadeAnimationRepository: ShadeAnimationRepository,
+) : ShadeAnimationInteractor(shadeAnimationRepository) {
+    override val isAnyCloseAnimationRunning = flowOf(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt
new file mode 100644
index 0000000..c4f4134
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import com.android.systemui.shade.data.repository.ShadeRepository
+import javax.inject.Inject
+
+/** Implementation of ShadeAnimationInteractor compatible with NPVC. */
+@SysUISingleton
+class ShadeAnimationInteractorLegacyImpl
+@Inject
+constructor(
+    shadeAnimationRepository: ShadeAnimationRepository,
+    shadeRepository: ShadeRepository,
+) : ShadeAnimationInteractor(shadeAnimationRepository) {
+    override val isAnyCloseAnimationRunning = shadeRepository.legacyIsClosing
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
new file mode 100644
index 0000000..1ee6d38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Implementation of ShadeAnimationInteractor compatible with the scene container framework. */
+@SysUISingleton
+class ShadeAnimationInteractorSceneContainerImpl
+@Inject
+constructor(
+    shadeAnimationRepository: ShadeAnimationRepository,
+    sceneInteractor: SceneInteractor,
+) : ShadeAnimationInteractor(shadeAnimationRepository) {
+    @OptIn(ExperimentalCoroutinesApi::class)
+    override val isAnyCloseAnimationRunning =
+        sceneInteractor.transitionState
+            .flatMapLatest { state ->
+                when (state) {
+                    is ObservableTransitionState.Idle -> flowOf(false)
+                    is ObservableTransitionState.Transition ->
+                        if (
+                            (state.fromScene == SceneKey.Shade &&
+                                state.toScene != SceneKey.QuickSettings) ||
+                                (state.fromScene == SceneKey.QuickSettings &&
+                                    state.toScene != SceneKey.Shade)
+                        ) {
+                            state.isUserInputOngoing.map { !it }
+                        } else {
+                            flowOf(false)
+                        }
+                }
+            }
+            .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 7cff8ea..3fd070c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -71,14 +71,11 @@
 
     override val isQsBypassingShade: Flow<Boolean> =
         sceneInteractor.transitionState
-            .flatMapLatest { state ->
+            .map { state ->
                 when (state) {
-                    is ObservableTransitionState.Idle -> flowOf(false)
+                    is ObservableTransitionState.Idle -> false
                     is ObservableTransitionState.Transition ->
-                        flowOf(
-                            state.toScene == SceneKey.QuickSettings &&
-                                state.fromScene != SceneKey.Shade
-                        )
+                        state.toScene == SceneKey.QuickSettings && state.fromScene != SceneKey.Shade
                 }
             }
             .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index d88fab0..ada7d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -153,7 +153,7 @@
     private static final int MSG_HIDE_TOAST                        = 53 << MSG_SHIFT;
     private static final int MSG_TRACING_STATE_CHANGED             = 54 << MSG_SHIFT;
     private static final int MSG_SUPPRESS_AMBIENT_DISPLAY          = 55 << MSG_SHIFT;
-    private static final int MSG_REQUEST_WINDOW_MAGNIFICATION_CONNECTION = 56 << MSG_SHIFT;
+    private static final int MSG_REQUEST_MAGNIFICATION_CONNECTION = 56 << MSG_SHIFT;
     //TODO(b/169175022) Update name and when feature name is locked.
     private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE      = 58 << MSG_SHIFT;
     private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT;
@@ -426,11 +426,11 @@
         /**
          * Requests {@link com.android.systemui.accessibility.Magnification} to invoke
          * {@code android.view.accessibility.AccessibilityManager#
-         * setWindowMagnificationConnection(IWindowMagnificationConnection)}
+         * setMagnificationConnection(IMagnificationConnection)}
          *
          * @param connect {@code true} if needs connection, otherwise set the connection to null.
          */
-        default void requestWindowMagnificationConnection(boolean connect) { }
+        default void requestMagnificationConnection(boolean connect) { }
 
         /**
          * @see IStatusBar#setNavigationBarLumaSamplingEnabled(int, boolean)
@@ -1125,9 +1125,9 @@
     }
 
     @Override
-    public void requestWindowMagnificationConnection(boolean connect) {
+    public void requestMagnificationConnection(boolean connect) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_REQUEST_WINDOW_MAGNIFICATION_CONNECTION, connect)
+            mHandler.obtainMessage(MSG_REQUEST_MAGNIFICATION_CONNECTION, connect)
                     .sendToTarget();
         }
     }
@@ -1767,9 +1767,9 @@
                         callbacks.suppressAmbientDisplay((boolean) msg.obj);
                     }
                     break;
-                case MSG_REQUEST_WINDOW_MAGNIFICATION_CONNECTION:
+                case MSG_REQUEST_MAGNIFICATION_CONNECTION:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).requestWindowMagnificationConnection((Boolean) msg.obj);
+                        mCallbacks.get(i).requestMagnificationConnection((Boolean) msg.obj);
                     }
                     break;
                 case MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index a36d36c..618dec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -278,6 +278,7 @@
         var contentInsets = state.contentRectForRotation(rot)
         tl.setPadding(0, state.paddingTop, 0, 0)
         (tl.layoutParams as FrameLayout.LayoutParams).apply {
+            topMargin = contentInsets.top
             height = contentInsets.height()
             if (rtl) {
                 width = contentInsets.left
@@ -290,6 +291,7 @@
         contentInsets = state.contentRectForRotation(rot)
         tr.setPadding(0, state.paddingTop, 0, 0)
         (tr.layoutParams as FrameLayout.LayoutParams).apply {
+            topMargin = contentInsets.top
             height = contentInsets.height()
             if (rtl) {
                 width = contentInsets.left
@@ -302,6 +304,7 @@
         contentInsets = state.contentRectForRotation(rot)
         br.setPadding(0, state.paddingTop, 0, 0)
         (br.layoutParams as FrameLayout.LayoutParams).apply {
+            topMargin = contentInsets.top
             height = contentInsets.height()
             if (rtl) {
                 width = contentInsets.left
@@ -314,6 +317,7 @@
         contentInsets = state.contentRectForRotation(rot)
         bl.setPadding(0, state.paddingTop, 0, 0)
         (bl.layoutParams as FrameLayout.LayoutParams).apply {
+            topMargin = contentInsets.top
             height = contentInsets.height()
             if (rtl) {
                 width = contentInsets.left
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index fec1765..118f5f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -87,8 +87,8 @@
             animationWindowView.addView(
                     it.view,
                     layoutParamsDefault(
-                            if (animationWindowView.isLayoutRtl) insets.first
-                            else insets.second))
+                            if (animationWindowView.isLayoutRtl) insets.left
+                            else insets.right))
             it.view.alpha = 0f
             // For some reason, the window view's measured width is always 0 here, so use the
             // parent (status bar)
@@ -289,7 +289,7 @@
      */
     private fun updateChipBounds(chip: BackgroundAnimatableView, contentArea: Rect) {
         // decide which direction we're animating from, and then set some screen coordinates
-        val chipTop = (contentArea.bottom - chip.view.measuredHeight) / 2
+        val chipTop = contentArea.top + (contentArea.height() - chip.view.measuredHeight) / 2
         val chipBottom = chipTop + chip.view.measuredHeight
         val chipRight: Int
         val chipLeft: Int
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index ef87406..599600d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -52,7 +52,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.WeatherData
+import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shared.regionsampling.RegionSampler
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index a2379b2..a0129ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -28,7 +28,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import java.io.PrintWriter;
 import java.util.HashMap;
@@ -55,14 +56,14 @@
  */
 // TODO(b/204468557): Move to @CoordinatorScope
 @SysUISingleton
-public class VisualStabilityCoordinator implements Coordinator, Dumpable,
-        ShadeStateEvents.ShadeStateEventsListener {
+public class VisualStabilityCoordinator implements Coordinator, Dumpable {
     public static final String TAG = "VisualStability";
     public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     private final DelayableExecutor mDelayableExecutor;
     private final HeadsUpManager mHeadsUpManager;
-    private final ShadeStateEvents mShadeStateEvents;
+    private final ShadeAnimationInteractor mShadeAnimationInteractor;
     private final StatusBarStateController mStatusBarStateController;
+    private final JavaAdapter mJavaAdapter;
     private final VisibilityLocationProvider mVisibilityLocationProvider;
     private final VisualStabilityProvider mVisualStabilityProvider;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -94,18 +95,20 @@
             DelayableExecutor delayableExecutor,
             DumpManager dumpManager,
             HeadsUpManager headsUpManager,
-            ShadeStateEvents shadeStateEvents,
+            ShadeAnimationInteractor shadeAnimationInteractor,
+            JavaAdapter javaAdapter,
             StatusBarStateController statusBarStateController,
             VisibilityLocationProvider visibilityLocationProvider,
             VisualStabilityProvider visualStabilityProvider,
             WakefulnessLifecycle wakefulnessLifecycle) {
         mHeadsUpManager = headsUpManager;
+        mShadeAnimationInteractor = shadeAnimationInteractor;
+        mJavaAdapter = javaAdapter;
         mVisibilityLocationProvider = visibilityLocationProvider;
         mVisualStabilityProvider = visualStabilityProvider;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mStatusBarStateController = statusBarStateController;
         mDelayableExecutor = delayableExecutor;
-        mShadeStateEvents = shadeStateEvents;
 
         dumpManager.registerDumpable(this);
     }
@@ -118,7 +121,10 @@
 
         mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
         mPulsing = mStatusBarStateController.isPulsing();
-        mShadeStateEvents.addShadeStateEventsListener(this);
+        mJavaAdapter.alwaysCollectFlow(mShadeAnimationInteractor.isAnyCloseAnimationRunning(),
+                this::onShadeOrQsClosingChanged);
+        mJavaAdapter.alwaysCollectFlow(mShadeAnimationInteractor.isLaunchingActivity(),
+                this::onLaunchingActivityChanged);
 
         pipeline.setVisualStabilityManager(mNotifStabilityManager);
     }
@@ -322,14 +328,12 @@
         }
     }
 
-    @Override
-    public void onPanelCollapsingChanged(boolean isCollapsing) {
-        mNotifPanelCollapsing = isCollapsing;
-        updateAllowedStates("notifPanelCollapsing", isCollapsing);
+    private void onShadeOrQsClosingChanged(boolean isClosing) {
+        mNotifPanelCollapsing = isClosing;
+        updateAllowedStates("notifPanelCollapsing", isClosing);
     }
 
-    @Override
-    public void onLaunchingActivityChanged(boolean isLaunchingActivity) {
+    private void onLaunchingActivityChanged(boolean isLaunchingActivity) {
         mNotifPanelLaunchingActivity = isLaunchingActivity;
         updateAllowedStates("notifPanelLaunchingActivity", isLaunchingActivity);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 0f14135..3a72205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -25,7 +25,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
-import com.android.systemui.shade.ShadeEventsModule;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -100,7 +99,6 @@
         CoordinatorsModule.class,
         FooterViewModelModule.class,
         KeyguardNotificationVisibilityProviderModule.class,
-        ShadeEventsModule.class,
         NotificationDataLayerModule.class,
         NotifPipelineChoreographerModule.class,
         NotificationSectionHeadersModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index b1e52af..ecca973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -305,8 +305,6 @@
                     }
                 }
             }
-            // Recalculate all icon positions, to reflect our updates.
-            view.calculateIconXTranslations()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
index 3a2e21a..0331654 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
@@ -21,6 +21,7 @@
 import com.android.internal.util.ContrastColorUtil
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.NO_COLOR
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
 import kotlinx.coroutines.flow.Flow
@@ -54,7 +55,8 @@
         iconColors.collect { colors ->
             val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
             val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
-            view.staticDrawableColor = colors.staticDrawableColor(view.viewBounds, isColorized)
+            view.staticDrawableColor =
+                if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR
             view.setDecorColor(colors.tint)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt
index 97d1e1b..2365db4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt
@@ -35,5 +35,5 @@
      * Returns the color to be applied to an icon, based on that icon's view bounds and whether or
      * not the notification icon is colorized.
      */
-    fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int
+    fun staticDrawableColor(viewBounds: Rect): Int
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index af37e49..6e5ac47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -117,8 +117,8 @@
         override val tint: Int,
         private val areas: Collection<Rect>,
     ) : NotificationIconColors {
-        override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int {
-            return if (isColorized && DarkIconDispatcher.isInAreas(areas, viewBounds)) {
+        override fun staticDrawableColor(viewBounds: Rect): Int {
+            return if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
                 tint
             } else {
                 DarkIconDispatcher.DEFAULT_ICON_TINT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
index 4ace194..a17c066 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
@@ -130,8 +130,10 @@
             }
             mListeners.put(uri, currentListeners);
             if (currentListeners.size() == 1) {
-                mSecureSettings.registerContentObserverForUser(
-                        uri, false, mContentObserver, mUserTracker.getUserId());
+                mBackgroundHandler.post(() -> {
+                    mSecureSettings.registerContentObserverForUser(
+                            uri, false, mContentObserver, mUserTracker.getUserId());
+                });
             }
         }
         mBackgroundHandler.post(() -> {
@@ -156,7 +158,9 @@
             }
 
             if (mListeners.size() == 0) {
-                mSecureSettings.unregisterContentObserver(mContentObserver);
+                mBackgroundHandler.post(() -> {
+                    mSecureSettings.unregisterContentObserver(mContentObserver);
+                });
             }
         }
         Trace.traceEnd(Trace.TRACE_TAG_APP);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 7aa7976..63194c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -71,6 +72,7 @@
     private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
     private val shadeControllerLazy: Lazy<ShadeController>,
     private val shadeViewControllerLazy: Lazy<ShadeViewController>,
+    private val shadeAnimationInteractor: ShadeAnimationInteractor,
     private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
     private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
@@ -863,6 +865,7 @@
                     return StatusBarLaunchAnimatorController(
                         animationController,
                         shadeViewControllerLazy.get(),
+                        shadeAnimationInteractor,
                         shadeControllerLazy.get(),
                         notifShadeWindowControllerLazy.get(),
                         isLaunchForActivity
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 8a64a50..145dbff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -24,11 +24,11 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Trace;
 import android.util.AttributeSet;
-import android.util.Pair;
 import android.util.TypedValue;
 import android.view.DisplayCutout;
 import android.view.Gravity;
@@ -103,7 +103,7 @@
     private DisplayCutout mDisplayCutout;
     private int mRoundedCornerPadding = 0;
     // right and left padding applied to this view to account for cutouts and rounded corners
-    private Pair<Integer, Integer> mPadding = new Pair(0, 0);
+    private Insets mPadding = Insets.of(0, 0, 0, 0);
 
     /**
      * The clipping on the top
@@ -184,7 +184,7 @@
 
         int marginStart = calculateMargin(
                 getResources().getDimensionPixelSize(R.dimen.keyguard_carrier_text_margin),
-                mPadding.first);
+                mPadding.left);
         lp.setMarginStart(marginStart);
 
         mCarrierLabel.setLayoutParams(lp);
@@ -303,9 +303,9 @@
 
         // consider privacy dot space
         final int minLeft = (isLayoutRtl() && mIsPrivacyDotEnabled)
-                ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first;
+                ? Math.max(mMinDotWidth, mPadding.left) : mPadding.left;
         final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled)
-                ? Math.max(mMinDotWidth, mPadding.second) : mPadding.second;
+                ? Math.max(mMinDotWidth, mPadding.right) : mPadding.right;
 
         setPadding(minLeft, waterfallTop, minRight, 0);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index a27e67b..cb7bc25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -20,10 +20,10 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.Pair;
 import android.view.DisplayCutout;
 import android.view.MotionEvent;
 import android.view.View;
@@ -271,13 +271,12 @@
     }
 
     private void updateSafeInsets() {
-        Pair<Integer, Integer> insets = mContentInsetsProvider
+        Insets insets = mContentInsetsProvider
                 .getStatusBarContentInsetsForCurrentRotation();
-
         setPadding(
-                insets.first,
-                getPaddingTop(),
-                insets.second,
+                insets.left,
+                insets.top,
+                insets.right,
                 getPaddingBottom());
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index cba72d0..3b96f57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -16,13 +16,16 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.annotation.Px
 import android.content.Context
 import android.content.res.Resources
+import android.graphics.Insets
 import android.graphics.Point
 import android.graphics.Rect
 import android.util.LruCache
 import android.util.Pair
 import android.view.DisplayCutout
+import android.view.Surface
 import androidx.annotation.VisibleForTesting
 import com.android.internal.policy.SystemBarUtils
 import com.android.systemui.Dumpable
@@ -154,13 +157,13 @@
     }
 
     /**
-     * Calculate the distance from the left and right edges of the screen to the status bar
+     * Calculate the distance from the left, right and top edges of the screen to the status bar
      * content area. This differs from the content area rects in that these values can be used
      * directly as padding.
      *
      * @param rotation the target rotation for which to calculate insets
      */
-    fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> =
+    fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
         traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
             val displayCutout = checkNotNull(context.display).cutout
             val key = getCacheKey(rotation, displayCutout)
@@ -175,15 +178,14 @@
             val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
                 rotation, displayCutout, getResourcesForRotation(rotation, context), key)
 
-            Pair(area.left, width - area.right)
+            Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0)
         }
 
     /**
-     * Calculate the left and right insets for the status bar content in the device's current
-     * rotation
+     * Calculate the insets for the status bar content in the device's current rotation
      * @see getStatusBarContentAreaForRotation
      */
-    fun getStatusBarContentInsetsForCurrentRotation(): Pair<Int, Int> {
+    fun getStatusBarContentInsetsForCurrentRotation(): Insets {
         return getStatusBarContentInsetsForRotation(getExactRotation(context))
     }
 
@@ -251,6 +253,10 @@
             minRight = max(minDotPadding, roundedCornerPadding)
         }
 
+        val bottomAlignedMargin = getBottomAlignedMargin(targetRotation, rotatedResources)
+        val statusBarContentHeight =
+                rotatedResources.getDimensionPixelSize(R.dimen.status_bar_icon_size_sp)
+
         return calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
@@ -260,7 +266,22 @@
                 minLeft,
                 minRight,
                 configurationController.isLayoutRtl,
-                dotWidth)
+                dotWidth,
+                bottomAlignedMargin,
+                statusBarContentHeight)
+    }
+
+    @Px
+    private fun getBottomAlignedMargin(targetRotation: Int, resources: Resources): Int {
+        val dimenRes =
+                when (targetRotation) {
+                    Surface.ROTATION_0 -> R.dimen.status_bar_bottom_aligned_margin_rotation_0
+                    Surface.ROTATION_90 -> R.dimen.status_bar_bottom_aligned_margin_rotation_90
+                    Surface.ROTATION_180 -> R.dimen.status_bar_bottom_aligned_margin_rotation_180
+                    Surface.ROTATION_270 -> R.dimen.status_bar_bottom_aligned_margin_rotation_270
+                    else -> throw IllegalStateException("Unknown rotation: $targetRotation")
+                }
+        return resources.getDimensionPixelSize(dimenRes)
     }
 
     fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int {
@@ -329,8 +350,7 @@
 }
 
 /**
- * Calculates the exact left and right positions for the status bar contents for the given
- * rotation
+ * Calculates the exact left and right positions for the status bar contents for the given rotation
  *
  * @param currentRotation current device rotation
  * @param targetRotation rotation for which to calculate the status bar content rect
@@ -341,9 +361,12 @@
  * @param minRight the minimum padding to enforce on the right
  * @param isRtl current layout direction is Right-To-Left or not
  * @param dotWidth privacy dot image width (0 if privacy dot is disabled)
- *
+ * @param bottomAlignedMargin the bottom margin that the status bar content should have. -1 if none,
+ *   and content should be centered vertically.
+ * @param statusBarContentHeight the height of the status bar contents (icons, text, etc)
  * @see [RotationUtils#getResourcesForRotation]
  */
+@VisibleForTesting
 fun calculateInsetsForRotationWithRotatedResources(
     @Rotation currentRotation: Int,
     @Rotation targetRotation: Int,
@@ -353,7 +376,9 @@
     minLeft: Int,
     minRight: Int,
     isRtl: Boolean,
-    dotWidth: Int
+    dotWidth: Int,
+    bottomAlignedMargin: Int,
+    statusBarContentHeight: Int
 ): Rect {
     /*
     TODO: Check if this is ever used for devices with no rounded corners
@@ -363,7 +388,7 @@
 
     val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation)
 
-    val sbLeftRight = getStatusBarLeftRight(
+    return getStatusBarContentBounds(
             displayCutout,
             statusBarHeight,
             rotZeroBounds.right,
@@ -375,9 +400,9 @@
             isRtl,
             dotWidth,
             targetRotation,
-            currentRotation)
-
-    return sbLeftRight
+            currentRotation,
+            bottomAlignedMargin,
+            statusBarContentHeight)
 }
 
 /**
@@ -399,26 +424,30 @@
  * @return a Rect which exactly calculates the Status Bar's content rect relative to the target
  * rotation
  */
-private fun getStatusBarLeftRight(
-    displayCutout: DisplayCutout?,
-    sbHeight: Int,
-    width: Int,
-    height: Int,
-    cWidth: Int,
-    cHeight: Int,
-    minLeft: Int,
-    minRight: Int,
-    isRtl: Boolean,
-    dotWidth: Int,
-    @Rotation targetRotation: Int,
-    @Rotation currentRotation: Int
+private fun getStatusBarContentBounds(
+        displayCutout: DisplayCutout?,
+        sbHeight: Int,
+        width: Int,
+        height: Int,
+        cWidth: Int,
+        cHeight: Int,
+        minLeft: Int,
+        minRight: Int,
+        isRtl: Boolean,
+        dotWidth: Int,
+        @Rotation targetRotation: Int,
+        @Rotation currentRotation: Int,
+        bottomAlignedMargin: Int,
+        statusBarContentHeight: Int
 ): Rect {
+    val insetTop = getInsetTop(bottomAlignedMargin, statusBarContentHeight, sbHeight)
+
     val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width
 
     val cutoutRects = displayCutout?.boundingRects
     if (cutoutRects == null || cutoutRects.isEmpty()) {
         return Rect(minLeft,
-                0,
+                insetTop,
                 logicalDisplayWidth - minRight,
                 sbHeight)
     }
@@ -455,7 +484,48 @@
         //                    is very close to but not directly touch edges.
     }
 
-    return Rect(leftMargin, 0, logicalDisplayWidth - rightMargin, sbHeight)
+    return Rect(leftMargin, insetTop, logicalDisplayWidth - rightMargin, sbHeight)
+}
+
+/*
+ * Returns the inset top of the status bar.
+ *
+ * Only greater than 0, when we want the content to be bottom aligned.
+ *
+ * Common case when we want content to be vertically centered within the status bar.
+ * Example dimensions:
+ * - Status bar height: 50dp
+ * - Content height: 20dp
+ *  _______________________________________________
+ *  |                                             |
+ *  |                                             |
+ *  | 09:00                            5G [] 74%  |  20dp Content CENTER_VERTICAL gravity
+ *  |                                             |
+ *  |_____________________________________________|
+ *
+ *  Case when we want bottom alignment and a bottom margin of 10dp.
+ *  We need to make the status bar height artificially smaller using top padding/inset.
+ *  - Status bar height: 50dp
+ *  - Content height: 20dp
+ *  - Bottom margin: 10dp
+ *   ______________________________________________
+ *  |_____________________________________________| 10dp top inset/padding
+ *  |                                             | 40dp new artificial status bar height
+ *  | 09:00                            5G [] 74%  | 20dp Content CENTER_VERTICAL gravity
+ *  |_____________________________________________| 10dp bottom margin
+ */
+@Px
+private fun getInsetTop(
+        bottomAlignedMargin: Int,
+        statusBarContentHeight: Int,
+        statusBarHeight: Int
+): Int {
+    val bottomAlignmentEnabled = bottomAlignedMargin >= 0
+    if (!bottomAlignmentEnabled) {
+        return 0
+    }
+    val newArtificialStatusBarHeight = bottomAlignedMargin * 2 + statusBarContentHeight
+    return statusBarHeight - newArtificialStatusBarHeight
 }
 
 private fun sbRect(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index b67ec58..8ca5bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -5,6 +5,7 @@
 import com.android.systemui.animation.LaunchAnimator
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
 import com.android.systemui.statusbar.NotificationShadeWindowController
 
 /**
@@ -14,6 +15,7 @@
 class StatusBarLaunchAnimatorController(
     private val delegate: ActivityLaunchAnimator.Controller,
     private val shadeViewController: ShadeViewController,
+    private val shadeAnimationInteractor: ShadeAnimationInteractor,
     private val shadeController: ShadeController,
     private val notificationShadeWindowController: NotificationShadeWindowController,
     private val isLaunchForActivity: Boolean = true
@@ -26,7 +28,7 @@
     override fun onIntentStarted(willAnimate: Boolean) {
         delegate.onIntentStarted(willAnimate)
         if (willAnimate) {
-            shadeViewController.setIsLaunchAnimationRunning(true)
+            shadeAnimationInteractor.setIsLaunchingActivity(true)
         } else {
             shadeController.collapseOnMainThread()
         }
@@ -34,7 +36,7 @@
 
     override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
         delegate.onLaunchAnimationStart(isExpandingFullyAbove)
-        shadeViewController.setIsLaunchAnimationRunning(true)
+        shadeAnimationInteractor.setIsLaunchingActivity(true)
         if (!isExpandingFullyAbove) {
             shadeViewController.collapseWithDuration(
                 ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
@@ -43,7 +45,7 @@
 
     override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
         delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
-        shadeViewController.setIsLaunchAnimationRunning(false)
+        shadeAnimationInteractor.setIsLaunchingActivity(false)
         shadeController.onLaunchAnimationEnd(isExpandingFullyAbove)
     }
 
@@ -58,7 +60,7 @@
 
     override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
         delegate.onLaunchAnimationCancelled()
-        shadeViewController.setIsLaunchAnimationRunning(false)
+        shadeAnimationInteractor.setIsLaunchingActivity(false)
         shadeController.onLaunchAnimationCancelled(isLaunchForActivity)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 2e1a077..9da6111 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -56,12 +56,12 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.DisplayId;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
 import com.android.systemui.statusbar.NotificationClickNotifier;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -117,7 +117,7 @@
     private final LockPatternUtils mLockPatternUtils;
     private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
     private final ActivityIntentHelper mActivityIntentHelper;
-    private final FeatureFlags mFeatureFlags;
+    private final ShadeAnimationInteractor mShadeAnimationInteractor;
 
     private final MetricsLogger mMetricsLogger;
     private final StatusBarNotificationActivityStarterLogger mLogger;
@@ -162,10 +162,10 @@
             ShadeViewController shadeViewController,
             NotificationShadeWindowController notificationShadeWindowController,
             ActivityLaunchAnimator activityLaunchAnimator,
+            ShadeAnimationInteractor shadeAnimationInteractor,
             NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
             LaunchFullScreenIntentProvider launchFullScreenIntentProvider,
             PowerInteractor powerInteractor,
-            FeatureFlags featureFlags,
             UserTracker userTracker) {
         mContext = context;
         mDisplayId = displayId;
@@ -188,7 +188,7 @@
         mStatusBarRemoteInputCallback = remoteInputCallback;
         mActivityIntentHelper = activityIntentHelper;
         mNotificationShadeWindowController = notificationShadeWindowController;
-        mFeatureFlags = featureFlags;
+        mShadeAnimationInteractor = shadeAnimationInteractor;
         mMetricsLogger = metricsLogger;
         mLogger = logger;
         mOnUserInteractionCallback = onUserInteractionCallback;
@@ -444,6 +444,7 @@
                     new StatusBarLaunchAnimatorController(
                             mNotificationAnimationProvider.getAnimatorController(row, null),
                             mShadeViewController,
+                            mShadeAnimationInteractor,
                             mShadeController,
                             mNotificationShadeWindowController,
                             isActivityIntent);
@@ -485,6 +486,7 @@
                             new StatusBarLaunchAnimatorController(
                                     mNotificationAnimationProvider.getAnimatorController(row),
                                     mShadeViewController,
+                                    mShadeAnimationInteractor,
                                     mShadeController,
                                     mNotificationShadeWindowController,
                                     true /* isActivityIntent */);
@@ -535,6 +537,7 @@
                                 : new StatusBarLaunchAnimatorController(
                                         viewController,
                                         mShadeViewController,
+                                        mShadeAnimationInteractor,
                                         mShadeController,
                                         mNotificationShadeWindowController,
                                         true /* isActivityIntent */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 2df30dc..93bc960 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -203,6 +203,28 @@
             SysUiState sysUiState,
             BroadcastDispatcher broadcastDispatcher,
             DialogLaunchAnimator dialogLaunchAnimator,
+            Delegate delegate) {
+        this(
+                context,
+                theme,
+                dismissOnDeviceLock,
+                featureFlags,
+                dialogManager,
+                sysUiState,
+                broadcastDispatcher,
+                dialogLaunchAnimator,
+                (DialogDelegate<SystemUIDialog>) delegate);
+    }
+
+    public SystemUIDialog(
+            Context context,
+            int theme,
+            boolean dismissOnDeviceLock,
+            FeatureFlags featureFlags,
+            SystemUIDialogManager dialogManager,
+            SysUiState sysUiState,
+            BroadcastDispatcher broadcastDispatcher,
+            DialogLaunchAnimator dialogLaunchAnimator,
             DialogDelegate<SystemUIDialog> delegate) {
         super(context, theme);
         mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java
index f5e9034..93db916 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SysuiDarkIconDispatcher.java
@@ -46,7 +46,8 @@
     /** Model for {@link #darkChangeFlow()} */
     class DarkChange {
 
-        public static final DarkChange EMPTY = new DarkChange(new ArrayList<>(), 0, 0);
+        public static final DarkChange EMPTY =
+                new DarkChange(new ArrayList<>(), /* darkIntensity= */ 0f, DEFAULT_ICON_TINT);
 
         public DarkChange(Collection<Rect> areas, float darkIntensity, int tint) {
             this.areas = areas;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 713283e..20d1fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -24,6 +24,7 @@
 import android.content.IntentFilter;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
+import android.icu.lang.UCharacter;
 import android.icu.text.DateTimePatternGenerator;
 import android.os.Bundle;
 import android.os.Handler;
@@ -472,7 +473,7 @@
                 if (a >= 0) {
                     // Move a back so any whitespace before AM/PM is also in the alternate size.
                     final int b = a;
-                    while (a > 0 && Character.isWhitespace(format.charAt(a-1))) {
+                    while (a > 0 && UCharacter.isUWhiteSpace(format.charAt(a - 1))) {
                         a--;
                     }
                     format = format.substring(0, a) + MAGIC1 + format.substring(a, b)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 75ae16e..0f2da2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -26,6 +26,10 @@
 import com.android.systemui.qs.tiles.UiModeNightTile
 import com.android.systemui.qs.tiles.WorkModeTile
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.alarm.domain.AlarmTileMapper
+import com.android.systemui.qs.tiles.impl.alarm.domain.interactor.AlarmTileDataInteractor
+import com.android.systemui.qs.tiles.impl.alarm.domain.interactor.AlarmTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
 import com.android.systemui.qs.tiles.impl.flashlight.domain.FlashlightMapper
 import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor
 import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor
@@ -59,6 +63,7 @@
     companion object {
         const val FLASHLIGHT_TILE_SPEC = "flashlight"
         const val LOCATION_TILE_SPEC = "location"
+        const val ALARM_TILE_SPEC = "alarm"
 
         /** Inject flashlight config */
         @Provides
@@ -123,6 +128,38 @@
                 stateInteractor,
                 mapper,
             )
+
+        /** Inject alarm config */
+        @Provides
+        @IntoMap
+        @StringKey(ALARM_TILE_SPEC)
+        fun provideAlarmTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(ALARM_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.ic_alarm,
+                        labelRes = R.string.status_bar_alarm,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject AlarmTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(ALARM_TILE_SPEC)
+        fun provideAlarmTileViewModel(
+            factory: QSTileViewModelFactory.Static<AlarmTileModel>,
+            mapper: AlarmTileMapper,
+            stateInteractor: AlarmTileDataInteractor,
+            userActionInteractor: AlarmTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(ALARM_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 
     /** Inject FlashlightTile into tileMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 39cdfa3..fa0cb5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -143,6 +143,8 @@
         mSetupObserver.register();
         mUserManager = context.getSystemService(UserManager.class);
         mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
+        // This registers the alarm broadcast receiver for the current user
+        mUserChangedCallback.onUserChanged(getCurrentUser(), context);
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
@@ -214,6 +216,7 @@
 
     @Override
     public long getNextAlarm() {
+        // TODO(b/314799105): Migrate usages to NextAlarmController
         final AlarmManager.AlarmClockInfo info = mAlarmManager.getNextAlarmClock(mUserId);
         return info != null ? info.getTriggerTime() : 0;
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index b403d1d..6f58bc2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -31,15 +31,16 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.LogBuffer
-import com.android.systemui.plugins.ClockAnimations
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockEvents
-import com.android.systemui.plugins.ClockFaceConfig
-import com.android.systemui.plugins.ClockFaceController
-import com.android.systemui.plugins.ClockFaceEvents
-import com.android.systemui.plugins.ClockTickRate
+import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFaceConfig
+import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockTickRate
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -97,6 +98,7 @@
     @Mock private lateinit var largeLogBuffer: LogBuffer
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var underTest: ClockEventController
+    @Mock private lateinit var zenModeController: ZenModeController
 
     @Before
     fun setUp() {
@@ -140,7 +142,8 @@
                 bgExecutor,
                 smallLogBuffer,
                 largeLogBuffer,
-                withDeps.featureFlags
+                withDeps.featureFlags,
+                zenModeController
             )
         underTest.clock = clock
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index adf0ada..2bbf0df 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -44,13 +44,13 @@
 import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
 import com.android.systemui.log.LogBuffer;
-import com.android.systemui.plugins.ClockAnimations;
-import com.android.systemui.plugins.ClockController;
-import com.android.systemui.plugins.ClockEvents;
-import com.android.systemui.plugins.ClockFaceConfig;
-import com.android.systemui.plugins.ClockFaceController;
-import com.android.systemui.plugins.ClockFaceEvents;
-import com.android.systemui.plugins.ClockTickRate;
+import com.android.systemui.plugins.clocks.ClockAnimations;
+import com.android.systemui.plugins.clocks.ClockController;
+import com.android.systemui.plugins.clocks.ClockEvents;
+import com.android.systemui.plugins.clocks.ClockFaceConfig;
+import com.android.systemui.plugins.clocks.ClockFaceController;
+import com.android.systemui.plugins.clocks.ClockFaceEvents;
+import com.android.systemui.plugins.clocks.ClockTickRate;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.clocks.AnimatableClockView;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index cb26e61..e8d86dd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -35,8 +35,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.plugins.ClockFaceConfig;
-import com.android.systemui.plugins.ClockTickRate;
+import com.android.systemui.plugins.clocks.ClockFaceConfig;
+import com.android.systemui.plugins.clocks.ClockTickRate;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.statusbar.StatusBarState;
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index e54b184..4508aea 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -40,10 +40,10 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ClockController;
-import com.android.systemui.plugins.ClockFaceController;
+import com.android.systemui.plugins.clocks.ClockController;
+import com.android.systemui.plugins.clocks.ClockFaceController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.StatusBarState;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 9c3288b..fad8552 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -37,8 +37,8 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.systemui.animation.ViewHierarchyAnimator;
-import com.android.systemui.plugins.ClockConfig;
-import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.clocks.ClockConfig;
+import com.android.systemui.plugins.clocks.ClockController;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
similarity index 83%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 4395282..235aa21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -33,8 +33,8 @@
 import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 
 import androidx.test.filters.SmallTest;
@@ -53,13 +53,13 @@
 import org.mockito.MockitoAnnotations;
 
 /**
- * Tests for {@link android.view.accessibility.IWindowMagnificationConnection} retrieved from
+ * Tests for {@link android.view.accessibility.IMagnificationConnection} retrieved from
  * {@link Magnification}
  */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class IWindowMagnificationConnectionTest extends SysuiTestCase {
+public class IMagnificationConnectionTest extends SysuiTestCase {
 
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     @Mock
@@ -85,7 +85,7 @@
     @Mock
     private AccessibilityLogger mA11yLogger;
 
-    private IWindowMagnificationConnection mIWindowMagnificationConnection;
+    private IMagnificationConnection mIMagnificationConnection;
     private Magnification mMagnification;
     private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
@@ -94,10 +94,10 @@
         MockitoAnnotations.initMocks(this);
         getContext().addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
         doAnswer(invocation -> {
-            mIWindowMagnificationConnection = invocation.getArgument(0);
+            mIMagnificationConnection = invocation.getArgument(0);
             return null;
-        }).when(mAccessibilityManager).setWindowMagnificationConnection(
-                any(IWindowMagnificationConnection.class));
+        }).when(mAccessibilityManager).setMagnificationConnection(
+                any(IMagnificationConnection.class));
         mMagnification = new Magnification(getContext(),
                 getContext().getMainThreadHandler(), mCommandQueue,
                 mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
@@ -107,14 +107,14 @@
         mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
                 mContext.getSystemService(DisplayManager.class));
 
-        mMagnification.requestWindowMagnificationConnection(true);
-        assertNotNull(mIWindowMagnificationConnection);
-        mIWindowMagnificationConnection.setConnectionCallback(mConnectionCallback);
+        mMagnification.requestMagnificationConnection(true);
+        assertNotNull(mIMagnificationConnection);
+        mIMagnificationConnection.setConnectionCallback(mConnectionCallback);
     }
 
     @Test
     public void enableWindowMagnification_passThrough() throws RemoteException {
-        mIWindowMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN,
+        mIMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN,
                 Float.NaN, 0f, 0f, mAnimationCallback);
         waitForIdleSync();
 
@@ -124,7 +124,7 @@
 
     @Test
     public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException {
-        mIWindowMagnificationConnection.disableWindowMagnification(TEST_DISPLAY,
+        mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY,
                 mAnimationCallback);
         waitForIdleSync();
 
@@ -134,7 +134,7 @@
 
     @Test
     public void setScaleForWindowMagnification() throws RemoteException {
-        mIWindowMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f);
+        mIMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f);
         waitForIdleSync();
 
         verify(mWindowMagnificationController).setScale(3.0f);
@@ -142,7 +142,7 @@
 
     @Test
     public void moveWindowMagnifier() throws RemoteException {
-        mIWindowMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
+        mIMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
         waitForIdleSync();
 
         verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f);
@@ -150,7 +150,7 @@
 
     @Test
     public void moveWindowMagnifierToPosition() throws RemoteException {
-        mIWindowMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY,
+        mIMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY,
                 100f, 200f, mAnimationCallback);
         waitForIdleSync();
 
@@ -163,7 +163,7 @@
         // magnification settings panel should not be showing
         assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
 
-        mIWindowMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
+        mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         waitForIdleSync();
 
@@ -173,7 +173,7 @@
 
     @Test
     public void removeMagnificationButton() throws RemoteException {
-        mIWindowMagnificationConnection.removeMagnificationButton(TEST_DISPLAY);
+        mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY);
         waitForIdleSync();
 
         verify(mModeSwitchesController).removeButton(TEST_DISPLAY);
@@ -181,7 +181,7 @@
 
     @Test
     public void removeMagnificationSettingsPanel() throws RemoteException {
-        mIWindowMagnificationConnection.removeMagnificationSettingsPanel(TEST_DISPLAY);
+        mIMagnificationConnection.removeMagnificationSettingsPanel(TEST_DISPLAY);
         waitForIdleSync();
 
         verify(mMagnificationSettingsController).closeMagnificationSettings();
@@ -191,7 +191,7 @@
     public void onUserMagnificationScaleChanged() throws RemoteException {
         final int testUserId = 1;
         final float testScale = 3.0f;
-        mIWindowMagnificationConnection.onUserMagnificationScaleChanged(
+        mIMagnificationConnection.onUserMagnificationScaleChanged(
                 testUserId, TEST_DISPLAY, testScale);
         waitForIdleSync();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index c972feb..39c8f5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -43,7 +43,7 @@
 import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 
 import androidx.test.filters.SmallTest;
@@ -98,11 +98,11 @@
         MockitoAnnotations.initMocks(this);
         getContext().addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
         doAnswer(invocation -> {
-            IWindowMagnificationConnection connection = invocation.getArgument(0);
+            IMagnificationConnection connection = invocation.getArgument(0);
             connection.setConnectionCallback(mConnectionCallback);
             return null;
-        }).when(mAccessibilityManager).setWindowMagnificationConnection(
-                any(IWindowMagnificationConnection.class));
+        }).when(mAccessibilityManager).setMagnificationConnection(
+                any(IMagnificationConnection.class));
 
         when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
 
@@ -138,22 +138,22 @@
 
     @Test
     public void requestWindowMagnificationConnection_setConnectionAndListener() {
-        mCommandQueue.requestWindowMagnificationConnection(true);
+        mCommandQueue.requestMagnificationConnection(true);
         waitForIdleSync();
 
-        verify(mAccessibilityManager).setWindowMagnificationConnection(any(
-                IWindowMagnificationConnection.class));
+        verify(mAccessibilityManager).setMagnificationConnection(any(
+                IMagnificationConnection.class));
 
-        mCommandQueue.requestWindowMagnificationConnection(false);
+        mCommandQueue.requestMagnificationConnection(false);
         waitForIdleSync();
 
-        verify(mAccessibilityManager).setWindowMagnificationConnection(isNull());
+        verify(mAccessibilityManager).setMagnificationConnection(isNull());
     }
 
     @Test
     public void onWindowMagnifierBoundsChanged() throws RemoteException {
         final Rect testBounds = new Rect(0, 0, 500, 600);
-        mCommandQueue.requestWindowMagnificationConnection(true);
+        mCommandQueue.requestMagnificationConnection(true);
         waitForIdleSync();
 
         mMagnification.mWindowMagnifierCallback
@@ -166,7 +166,7 @@
     public void onPerformScaleAction_enabled_notifyCallback() throws RemoteException {
         final float newScale = 4.0f;
         final boolean updatePersistence = true;
-        mCommandQueue.requestWindowMagnificationConnection(true);
+        mCommandQueue.requestMagnificationConnection(true);
         waitForIdleSync();
 
         mMagnification.mWindowMagnifierCallback
@@ -178,7 +178,7 @@
 
     @Test
     public void onAccessibilityActionPerformed_enabled_notifyCallback() throws RemoteException {
-        mCommandQueue.requestWindowMagnificationConnection(true);
+        mCommandQueue.requestMagnificationConnection(true);
         waitForIdleSync();
 
         mMagnification.mWindowMagnifierCallback
@@ -189,7 +189,7 @@
 
     @Test
     public void onMove_enabled_notifyCallback() throws RemoteException {
-        mCommandQueue.requestWindowMagnificationConnection(true);
+        mCommandQueue.requestMagnificationConnection(true);
         waitForIdleSync();
 
         mMagnification.mWindowMagnifierCallback.onMove(TEST_DISPLAY);
@@ -254,7 +254,7 @@
 
     @Test
     public void onMagnifierScale_notifyCallback() throws RemoteException {
-        mCommandQueue.requestWindowMagnificationConnection(true);
+        mCommandQueue.requestMagnificationConnection(true);
         waitForIdleSync();
         final float scale = 3.0f;
         final boolean updatePersistence = false;
@@ -271,7 +271,7 @@
     public void onModeSwitch_windowEnabledAndSwitchToFullscreen_hidePanelAndNotifyCallback()
             throws RemoteException {
         when(mWindowMagnificationController.isActivated()).thenReturn(true);
-        mCommandQueue.requestWindowMagnificationConnection(true);
+        mCommandQueue.requestMagnificationConnection(true);
         waitForIdleSync();
 
         mMagnification.mMagnificationSettingsControllerCallback.onModeSwitch(
@@ -289,7 +289,7 @@
     public void onModeSwitch_switchToSameMode_doNothing()
             throws RemoteException {
         when(mWindowMagnificationController.isActivated()).thenReturn(true);
-        mCommandQueue.requestWindowMagnificationConnection(true);
+        mCommandQueue.requestMagnificationConnection(true);
         waitForIdleSync();
 
         mMagnification.mMagnificationSettingsControllerCallback.onModeSwitch(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
similarity index 71%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index 83bee93..bfb5485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -20,18 +20,24 @@
 import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.LayoutInflater
 import android.view.ViewGroup
 import android.widget.Button
 import android.widget.SeekBar
 import androidx.test.filters.SmallTest
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.model.SysUiState
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SystemSettings
@@ -40,25 +46,25 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 private const val ON: Int = 1
 private const val OFF: Int = 0
 
-/** Tests for [FontScalingDialog]. */
+/** Tests for [FontScalingDialogDelegate]. */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-class FontScalingDialogTest : SysuiTestCase() {
-    private val MIN_UPDATE_INTERVAL_MS: Long = 800
-    private val CHANGE_BY_SEEKBAR_DELAY_MS: Long = 100
-    private val CHANGE_BY_BUTTON_DELAY_MS: Long = 300
-    private lateinit var fontScalingDialog: FontScalingDialog
+class FontScalingDialogDelegateTest : SysuiTestCase() {
+    private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
+    private lateinit var dialog: SystemUIDialog
     private lateinit var systemSettings: SystemSettings
     private lateinit var secureSettings: SecureSettings
     private lateinit var systemClock: FakeSystemClock
@@ -69,9 +75,12 @@
             .getResources()
             .getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
 
+    @Mock private lateinit var dialogManager: SystemUIDialogManager
+    @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
     @Mock private lateinit var userTracker: UserTracker
-    @Captor
-    private lateinit var seekBarChangeCaptor: ArgumentCaptor<OnSeekBarWithIconButtonsChangeListener>
+    private val featureFlags = FakeFeatureFlags()
+    @Mock private lateinit var sysuiState: SysUiState
+    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
 
     @Before
     fun setUp() {
@@ -79,28 +88,46 @@
         testableLooper = TestableLooper.get(this)
         val mainHandler = Handler(testableLooper.looper)
         systemSettings = FakeSettings()
+        featureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true)
         // Guarantee that the systemSettings always starts with the default font scale.
         systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
         secureSettings = FakeSettings()
         systemClock = FakeSystemClock()
         backgroundDelayableExecutor = FakeExecutor(systemClock)
-        fontScalingDialog =
-            FontScalingDialog(
+        whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+
+        fontScalingDialogDelegate = spy(FontScalingDialogDelegate(
                 mContext,
+                dialogFactory,
+                LayoutInflater.from(mContext),
                 systemSettings,
                 secureSettings,
                 systemClock,
                 userTracker,
                 mainHandler,
                 backgroundDelayableExecutor
-            )
+            ))
+
+        dialog = SystemUIDialog(
+            mContext,
+            0,
+            DEFAULT_DISMISS_ON_DEVICE_LOCK,
+            featureFlags,
+            dialogManager,
+            sysuiState,
+            fakeBroadcastDispatcher,
+            dialogLaunchAnimator,
+            fontScalingDialogDelegate
+        )
+
+        whenever(dialogFactory.create(any())).thenReturn(dialog)
     }
 
     @Test
     fun showTheDialog_seekbarIsShowingCorrectProgress() {
-        fontScalingDialog.show()
+        dialog.show()
 
-        val seekBar: SeekBar = fontScalingDialog.findViewById<SeekBar>(R.id.seekbar)!!
+        val seekBar: SeekBar = dialog.findViewById<SeekBar>(R.id.seekbar)!!
         val progress: Int = seekBar.getProgress()
         val currentScale =
             systemSettings.getFloatForUser(
@@ -111,17 +138,17 @@
 
         assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
 
-        fontScalingDialog.dismiss()
+        dialog.dismiss()
     }
 
     @Test
     fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() {
-        fontScalingDialog.show()
+        dialog.show()
 
-        val iconEndFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_end_frame)!!
+        val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!!
         val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
-            fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
-        val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
+            dialog.findViewById(R.id.font_scaling_slider)!!
+        val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!!
 
         seekBarWithIconButtonsView.setProgress(0)
         backgroundDelayableExecutor.runAllReady()
@@ -142,17 +169,17 @@
         assertThat(seekBar.getProgress()).isEqualTo(1)
         assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
 
-        fontScalingDialog.dismiss()
+        dialog.dismiss()
     }
 
     @Test
     fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() {
-        fontScalingDialog.show()
+        dialog.show()
 
-        val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!!
+        val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!!
         val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
-            fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
-        val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
+            dialog.findViewById(R.id.font_scaling_slider)!!
+        val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!!
 
         seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
         backgroundDelayableExecutor.runAllReady()
@@ -174,14 +201,14 @@
         assertThat(currentScale)
             .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat())
 
-        fontScalingDialog.dismiss()
+        dialog.dismiss()
     }
 
     @Test
     fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
-        fontScalingDialog.show()
+        dialog.show()
 
-        val iconStartFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_start_frame)!!
+        val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!!
         secureSettings.putIntForUser(
             Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
             OFF,
@@ -202,24 +229,21 @@
             )
         assertThat(currentSettings).isEqualTo(ON)
 
-        fontScalingDialog.dismiss()
+        dialog.dismiss()
     }
 
     @Test
     fun dragSeekbar_systemFontSizeSettingsDoesNotChange() {
-        fontScalingDialog = spy(fontScalingDialog)
-        val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext))
-        whenever(
-                fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)
-            )
-            .thenReturn(slider)
-        fontScalingDialog.show()
-        verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
+        dialog.show()
+
+        val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!!
+        val changeListener = slider.onSeekBarWithIconButtonsChangeListener
+
         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
 
         // Default seekbar progress for font size is 1, simulate dragging to 0 without
         // releasing the finger.
-        seekBarChangeCaptor.value.onStartTrackingTouch(seekBar)
+        changeListener.onStartTrackingTouch(seekBar)
         // Update seekbar progress. This will trigger onProgressChanged in the
         // OnSeekBarChangeListener and the seekbar could get updated progress value
         // in onStopTrackingTouch.
@@ -238,13 +262,13 @@
         assertThat(systemScale).isEqualTo(1.0f)
 
         // Simulate releasing the finger from the seekbar.
-        seekBarChangeCaptor.value.onStopTrackingTouch(seekBar)
+        changeListener.onStopTrackingTouch(seekBar)
         backgroundDelayableExecutor.runAllReady()
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
         // SeekBar interaction is finalized.
-        seekBarChangeCaptor.value.onUserInteractionFinalized(
+        changeListener.onUserInteractionFinalized(
             seekBar,
             OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER
         )
@@ -261,25 +285,21 @@
             )
         assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat())
 
-        fontScalingDialog.dismiss()
+        dialog.dismiss()
     }
 
     @Test
     fun dragSeekBar_createTextPreview() {
-        fontScalingDialog = spy(fontScalingDialog)
-        val slider: SeekBarWithIconButtonsView = spy(SeekBarWithIconButtonsView(mContext))
-        whenever(
-                fontScalingDialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)
-            )
-            .thenReturn(slider)
-        fontScalingDialog.show()
-        verify(slider).setOnSeekBarWithIconButtonsChangeListener(capture(seekBarChangeCaptor))
+        dialog.show()
+        val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!!
+        val changeListener = slider.onSeekBarWithIconButtonsChangeListener
+
         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
 
         // Default seekbar progress for font size is 1, simulate dragging to 0 without
         // releasing the finger
-        seekBarChangeCaptor.value.onStartTrackingTouch(seekBar)
-        seekBarChangeCaptor.value.onProgressChanged(
+        changeListener.onStartTrackingTouch(seekBar)
+        changeListener.onProgressChanged(
             seekBar,
             /* progress= */ 0,
             /* fromUser= */ false
@@ -287,16 +307,16 @@
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
-        verify(fontScalingDialog).createTextPreview(/* index= */ 0)
-        fontScalingDialog.dismiss()
+        verify(fontScalingDialogDelegate).createTextPreview(/* index= */ 0)
+        dialog.dismiss()
     }
 
     @Test
     fun changeFontSize_buttonIsDisabledBeforeFontSizeChangeFinishes() {
-        fontScalingDialog.show()
+        dialog.show()
 
-        val iconEndFrame: ViewGroup = fontScalingDialog.findViewById(R.id.icon_end_frame)!!
-        val doneButton: Button = fontScalingDialog.findViewById(com.android.internal.R.id.button1)!!
+        val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!!
+        val doneButton: Button = dialog.findViewById(com.android.internal.R.id.button1)!!
 
         iconEndFrame.performClick()
         backgroundDelayableExecutor.runAllReady()
@@ -308,7 +328,7 @@
 
         val config = Configuration()
         config.fontScale = 1.15f
-        fontScalingDialog.onConfigurationChanged(config)
+        dialog.onConfigurationChanged(config)
         testableLooper.processAllMessages()
         assertThat(doneButton.isEnabled).isTrue()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 9671966..c425e82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -444,6 +444,7 @@
 
         verify(mClipboardOverlayView, never()).setMinimized(true);
         verify(mClipboardOverlayView).setMinimized(false);
+        verify(mClipboardOverlayView).getEnterAnimation();
         verify(mClipboardOverlayView).showTextPreview("Test Item", false);
     }
 
@@ -458,6 +459,7 @@
 
         verify(mClipboardOverlayView).setMinimized(true);
         verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHOWN_MINIMIZED, 0, "abc");
+        verify(mClipboardOverlayView).getEnterAnimation();
         verify(mClipboardOverlayView, never()).setMinimized(false);
         verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean());
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
index 0981c62..a4d217f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
@@ -22,10 +22,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.ClockConfig
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockFaceController
-import com.android.systemui.plugins.ClockFaceLayout
+import com.android.systemui.plugins.clocks.ClockConfig
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockFaceLayout
 import com.android.systemui.util.mockito.whenever
 import kotlin.test.Test
 import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index f067871..d421004 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -30,9 +30,9 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockFaceConfig
-import com.android.systemui.plugins.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFaceConfig
+import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index bc0e416..23a2709 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -21,7 +21,6 @@
 
 import android.view.View
 import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardClockSwitch.LARGE
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
 import com.android.systemui.SysuiTestCase
@@ -43,7 +42,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.ClockController
+import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.dozeParameters
@@ -73,9 +72,10 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class KeyguardRootViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos().apply {
-        featureFlagsClassic.apply { set(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT, false) }
-    }
+    private val kosmos =
+        testKosmos().apply {
+            featureFlagsClassic.apply { set(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT, false) }
+        }
     private val testScope = kosmos.testScope
     private val repository = kosmos.fakeKeyguardRepository
     private val configurationRepository = kosmos.fakeConfigurationRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index d1d3c17..7796452 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
@@ -31,13 +32,12 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
@@ -45,9 +45,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
@@ -64,8 +64,9 @@
     @Mock private lateinit var qsLogger: QSLogger
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
     @Mock private lateinit var uiEventLogger: QsEventLogger
-    @Mock private lateinit var userTracker: UserTracker
     @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
+    @Mock private lateinit var dialog: SystemUIDialog
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var systemClock: FakeSystemClock
@@ -79,6 +80,7 @@
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         `when`(qsHost.getContext()).thenReturn(mContext)
+        `when`(fontScalingDialogDelegate.createDialog()).thenReturn(dialog)
         systemClock = FakeSystemClock()
         backgroundDelayableExecutor = FakeExecutor(systemClock)
 
@@ -95,11 +97,7 @@
                 qsLogger,
                 keyguardStateController,
                 dialogLaunchAnimator,
-                FakeSettings(),
-                FakeSettings(),
-                FakeSystemClock(),
-                userTracker,
-                backgroundDelayableExecutor,
+                { fontScalingDialogDelegate },
             )
         fontScalingTile.initialize()
         testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index b0b29e5..daf0654 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -127,7 +127,10 @@
 import com.android.systemui.scene.SceneTestUtils;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
 import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
 import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
@@ -347,6 +350,7 @@
     protected KeyguardClockInteractor mKeyguardClockInteractor;
     protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected KeyguardInteractor mKeyguardInteractor;
+    protected ShadeAnimationInteractor mShadeAnimationInteractor;
     protected SceneTestUtils mUtils = new SceneTestUtils(this);
     protected TestScope mTestScope = mUtils.getTestScope();
     protected ShadeInteractor mShadeInteractor;
@@ -393,6 +397,8 @@
         mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
         mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
         mShadeRepository = new FakeShadeRepository();
+        mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl(
+                new ShadeAnimationRepository(), mShadeRepository);
         mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
         when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
                 StateFlowKt.MutableStateFlow(false));
@@ -651,7 +657,7 @@
                 mStatusBarWindowStateController,
                 mNotificationShadeWindowController,
                 mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
-                mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
+                mLatencyTracker, mAccessibilityManager, 0, mUpdateMonitor,
                 mMetricsLogger,
                 mShadeLog,
                 mConfigurationController,
@@ -715,6 +721,7 @@
                 mActivityStarter,
                 mSharedNotificationContainerInteractor,
                 mActiveNotificationsInteractor,
+                mShadeAnimationInteractor,
                 mKeyguardViewConfigurator,
                 mKeyguardFaceAuthInteractor,
                 new ResourcesSplitShadeStateController(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 56061f6..9fa173a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -83,6 +83,7 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
+import android.graphics.Insets
 import org.mockito.junit.MockitoJUnit
 
 private val EMPTY_CHANGES = ConstraintsChanges()
@@ -930,12 +931,16 @@
         return windowInsets
     }
 
-    private fun mockInsetsProvider(
-        insets: Pair<Int, Int> = 0 to 0,
-        cornerCutout: Boolean = false,
-    ) {
+    private fun mockInsetsProvider(insets: Pair<Int, Int> = 0 to 0, cornerCutout: Boolean = false) {
         whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(insets.toAndroidPair())
+                .thenReturn(
+                        Insets.of(
+                                /* left= */ insets.first,
+                                /* top= */ 0,
+                                /* right= */ insets.second,
+                                /* bottom= */ 0
+                        )
+                )
         whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(cornerCutout)
     }
 
@@ -980,7 +985,7 @@
             )
             .thenReturn(EMPTY_CHANGES)
         whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(Pair(0, 0).toAndroidPair())
+            .thenReturn(Insets.NONE)
         whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false)
         setupCurrentInsets(null)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index 5f8777d..f8aa359 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -225,4 +225,13 @@
             underTest.setLegacyQsFullscreen(true)
             assertThat(underTest.legacyQsFullscreen.value).isEqualTo(true)
         }
+
+    @Test
+    fun updateLegacyIsClosing() =
+        testScope.runTest {
+            assertThat(underTest.legacyIsClosing.value).isEqualTo(false)
+
+            underTest.setLegacyIsClosing(true)
+            assertThat(underTest.legacyIsClosing.value).isEqualTo(true)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
new file mode 100644
index 0000000..6bbe900c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.TestMocksModule
+import com.android.systemui.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.Flags
+import com.android.systemui.runCurrent
+import com.android.systemui.runTest
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth
+import dagger.BindsInstance
+import dagger.Component
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import org.junit.Test
+
+@SmallTest
+class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() {
+
+    @SysUISingleton
+    @Component(
+        modules =
+            [
+                SysUITestModule::class,
+                UserDomainLayerModule::class,
+            ]
+    )
+    interface TestComponent : SysUITestComponent<ShadeAnimationInteractorSceneContainerImpl> {
+        val sceneInteractor: SceneInteractor
+
+        @Component.Factory
+        interface Factory {
+            fun create(
+                @BindsInstance test: SysuiTestCase,
+                featureFlags: FakeFeatureFlagsClassicModule,
+                mocks: TestMocksModule,
+            ): TestComponent
+        }
+    }
+
+    private val dozeParameters: DozeParameters = mock()
+
+    private val testComponent: TestComponent =
+        DaggerShadeAnimationInteractorSceneContainerImplTest_TestComponent.factory()
+            .create(
+                test = this,
+                featureFlags =
+                    FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
+                mocks =
+                    TestMocksModule(
+                        dozeParameters = dozeParameters,
+                    ),
+            )
+
+    @Test
+    fun isAnyCloseAnimationRunning_qsToShade() =
+        testComponent.runTest() {
+            val actual by collectLastValue(underTest.isAnyCloseAnimationRunning)
+
+            // WHEN transitioning from QS to Shade
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.QuickSettings,
+                        toScene = SceneKey.Shade,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is animating closed
+            Truth.assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun isAnyCloseAnimationRunning_qsToGone_userInputNotOngoing() =
+        testComponent.runTest() {
+            val actual by collectLastValue(underTest.isAnyCloseAnimationRunning)
+
+            // WHEN transitioning from QS to Gone with no ongoing user input
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.QuickSettings,
+                        toScene = SceneKey.Gone,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is animating closed
+            Truth.assertThat(actual).isTrue()
+        }
+
+    @Test
+    fun isAnyCloseAnimationRunning_qsToGone_userInputOngoing() =
+        testComponent.runTest() {
+            val actual by collectLastValue(underTest.isAnyCloseAnimationRunning)
+
+            // WHEN transitioning from QS to Gone with user input ongoing
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.QuickSettings,
+                        toScene = SceneKey.Gone,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(true),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is not animating closed
+            Truth.assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun updateIsLaunchingActivity() =
+        testComponent.runTest {
+            Truth.assertThat(underTest.isLaunchingActivity.value).isEqualTo(false)
+
+            underTest.setIsLaunchingActivity(true)
+            Truth.assertThat(underTest.isLaunchingActivity.value).isEqualTo(true)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index 565e20a..310b86f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -127,22 +127,22 @@
             val actual by collectLastValue(underTest.qsExpansion)
 
             // WHEN split shade is enabled and QS is expanded
-            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
             overrideResource(R.bool.config_use_split_notification_shade, true)
             configurationRepository.onAnyConfigurationChange()
-            val progress = MutableStateFlow(.3f)
+            runCurrent()
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
                         fromScene = SceneKey.QuickSettings,
                         toScene = SceneKey.Shade,
-                        progress = progress,
+                        progress = MutableStateFlow(.3f),
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
                     )
                 )
             sceneInteractor.setTransitionState(transitionState)
             runCurrent()
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
 
             // THEN legacy shade expansion is passed through
             Truth.assertThat(actual).isEqualTo(.3f)
@@ -157,6 +157,8 @@
             // WHEN split shade is not enabled and QS is expanded
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
             overrideResource(R.bool.config_use_split_notification_shade, false)
+            configurationRepository.onAnyConfigurationChange()
+            runCurrent()
             val progress = MutableStateFlow(.3f)
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
@@ -182,13 +184,12 @@
 
             // WHEN scene transition active
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
-            val progress = MutableStateFlow(.3f)
             val transitionState =
                 MutableStateFlow<ObservableTransitionState>(
                     ObservableTransitionState.Transition(
                         fromScene = SceneKey.QuickSettings,
                         toScene = SceneKey.Shade,
-                        progress = progress,
+                        progress = MutableStateFlow(.3f),
                         isInitiatedByUserInput = false,
                         isUserInputOngoing = flowOf(false),
                     )
@@ -347,6 +348,52 @@
             Truth.assertThat(expansionAmount).isEqualTo(0f)
         }
 
+    fun isQsBypassingShade_goneToQs() =
+        testComponent.runTest() {
+            val actual by collectLastValue(underTest.isQsBypassingShade)
+
+            // WHEN transitioning from QS directly to Gone
+            configurationRepository.onAnyConfigurationChange()
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Gone,
+                        toScene = SceneKey.QuickSettings,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is bypassing shade
+            Truth.assertThat(actual).isTrue()
+        }
+
+    fun isQsBypassingShade_shadeToQs() =
+        testComponent.runTest() {
+            val actual by collectLastValue(underTest.isQsBypassingShade)
+
+            // WHEN transitioning from QS to Shade
+            configurationRepository.onAnyConfigurationChange()
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Shade,
+                        toScene = SceneKey.QuickSettings,
+                        progress = MutableStateFlow(.1f),
+                        isInitiatedByUserInput = false,
+                        isUserInputOngoing = flowOf(false),
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+            runCurrent()
+
+            // THEN qs is not bypassing shade
+            Truth.assertThat(actual).isFalse()
+        }
+
     @Test
     fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() =
         testComponent.runTest() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ae659f4..ee94cbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -24,11 +24,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.TRANSIT_CLOCK
-import com.android.systemui.plugins.ClockController
-import com.android.systemui.plugins.ClockId
-import com.android.systemui.plugins.ClockMetadata
-import com.android.systemui.plugins.ClockProviderPlugin
-import com.android.systemui.plugins.ClockSettings
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockProviderPlugin
+import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.plugins.PluginLifecycleManager
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
@@ -381,12 +381,15 @@
     }
 
     @Test
-    fun knownPluginAttached_clockAndListChanged_notLoaded() {
-        val lifecycle1 = FakeLifecycle("Metro", null).apply {
-            mComponentName = ComponentName("com.android.systemui.clocks.metro", "MetroClock")
+    fun knownPluginAttached_clockAndListChanged_loadedCurrent() {
+        val metroLifecycle = FakeLifecycle("Metro", null).apply {
+            mComponentName = ComponentName("com.android.systemui.clocks.metro", "Metro")
         }
-        val lifecycle2 = FakeLifecycle("BigNum", null).apply {
-            mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNumClock")
+        val bignumLifecycle = FakeLifecycle("BigNum", null).apply {
+            mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNum")
+        }
+        val calligraphyLifecycle = FakeLifecycle("Calligraphy", null).apply {
+            mComponentName = ComponentName("com.android.systemui.clocks.calligraphy", "Calligraphy")
         }
 
         var changeCallCount = 0
@@ -401,15 +404,21 @@
         assertEquals(1, changeCallCount)
         assertEquals(0, listChangeCallCount)
 
-        assertEquals(false, pluginListener.onPluginAttached(lifecycle1))
+        assertEquals(false, pluginListener.onPluginAttached(metroLifecycle))
         scheduler.runCurrent()
         assertEquals(1, changeCallCount)
         assertEquals(1, listChangeCallCount)
 
-        assertEquals(false, pluginListener.onPluginAttached(lifecycle2))
+        assertEquals(false, pluginListener.onPluginAttached(bignumLifecycle))
         scheduler.runCurrent()
         assertEquals(1, changeCallCount)
         assertEquals(2, listChangeCallCount)
+
+        // This returns true, but doesn't trigger onCurrentClockChanged yet
+        assertEquals(true, pluginListener.onPluginAttached(calligraphyLifecycle))
+        scheduler.runCurrent()
+        assertEquals(1, changeCallCount)
+        assertEquals(3, listChangeCallCount)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index bd3dae4..fef262f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -24,9 +24,9 @@
 import android.view.LayoutInflater
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
-import com.android.systemui.customization.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.ClockSettings
+import com.android.systemui.customization.R
+import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index b04d5d3..260bef8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -503,10 +503,10 @@
     }
 
     @Test
-    public void testRequestWindowMagnificationConnection() {
-        mCommandQueue.requestWindowMagnificationConnection(true);
+    public void testRequestMagnificationConnection() {
+        mCommandQueue.requestMagnificationConnection(true);
         waitForIdleSync();
-        verify(mCallbacks).requestWindowMagnificationConnection(true);
+        verify(mCallbacks).requestMagnificationConnection(true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
new file mode 100644
index 0000000..2951fc0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.FakeStatusBarStateController
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.test.TestScope
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class PrivacyDotViewControllerTest : SysuiTestCase() {
+
+    private val context = getContext().createDisplayContext(createMockDisplay())
+
+    private val testScope = TestScope()
+    private val executor = InstantExecutor()
+    private val statusBarStateController = FakeStatusBarStateController()
+    private val configurationController = FakeConfigurationController()
+    private val contentInsetsProvider = createMockContentInsetsProvider()
+
+    private val topLeftView = initDotView()
+    private val topRightView = initDotView()
+    private val bottomLeftView = initDotView()
+    private val bottomRightView = initDotView()
+
+    private val controller =
+        PrivacyDotViewController(
+                executor,
+                testScope.backgroundScope,
+                statusBarStateController,
+                configurationController,
+                contentInsetsProvider,
+                animationScheduler = mock<SystemStatusAnimationScheduler>(),
+                shadeInteractor = null
+            )
+            .also {
+                it.setUiExecutor(executor)
+                it.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+            }
+
+    @Test
+    fun topMargin_topLeftView_basedOnSeascapeArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(topLeftView.frameLayoutParams.topMargin)
+            .isEqualTo(CONTENT_AREA_ROTATION_SEASCAPE.top)
+    }
+
+    @Test
+    fun topMargin_topRightView_basedOnPortraitArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(topRightView.frameLayoutParams.topMargin)
+            .isEqualTo(CONTENT_AREA_ROTATION_NONE.top)
+    }
+
+    @Test
+    fun topMargin_bottomLeftView_basedOnUpsideDownArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(bottomLeftView.frameLayoutParams.topMargin)
+            .isEqualTo(CONTENT_AREA_ROTATION_UPSIDE_DOWN.top)
+    }
+
+    @Test
+    fun topMargin_bottomRightView_basedOnLandscapeArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(bottomRightView.frameLayoutParams.topMargin)
+            .isEqualTo(CONTENT_AREA_ROTATION_LANDSCAPE.top)
+    }
+
+    @Test
+    fun height_topLeftView_basedOnSeascapeAreaHeight() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(topLeftView.layoutParams.height)
+            .isEqualTo(CONTENT_AREA_ROTATION_SEASCAPE.height())
+    }
+
+    @Test
+    fun height_topRightView_basedOnPortraitAreaHeight() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(topRightView.layoutParams.height).isEqualTo(CONTENT_AREA_ROTATION_NONE.height())
+    }
+
+    @Test
+    fun height_bottomLeftView_basedOnUpsidedownAreaHeight() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(bottomLeftView.layoutParams.height)
+            .isEqualTo(CONTENT_AREA_ROTATION_UPSIDE_DOWN.height())
+    }
+
+    @Test
+    fun height_bottomRightView_basedOnLandscapeAreaHeight() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(bottomRightView.layoutParams.height)
+            .isEqualTo(CONTENT_AREA_ROTATION_LANDSCAPE.height())
+    }
+
+    @Test
+    fun width_topLeftView_ltr_basedOnDisplayHeightAndSeascapeArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(topLeftView.layoutParams.width)
+            .isEqualTo(DISPLAY_HEIGHT - CONTENT_AREA_ROTATION_SEASCAPE.right)
+    }
+
+    @Test
+    fun width_topLeftView_rtl_basedOnPortraitArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+        configurationController.notifyLayoutDirectionChanged(isRtl = true)
+
+        assertThat(topLeftView.layoutParams.width).isEqualTo(CONTENT_AREA_ROTATION_NONE.left)
+    }
+
+    @Test
+    fun width_topRightView_ltr_basedOnPortraitArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(topRightView.layoutParams.width)
+            .isEqualTo(DISPLAY_WIDTH - CONTENT_AREA_ROTATION_NONE.right)
+    }
+
+    @Test
+    fun width_topRightView_rtl_basedOnLandscapeArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+        configurationController.notifyLayoutDirectionChanged(isRtl = true)
+
+        assertThat(topRightView.layoutParams.width).isEqualTo(CONTENT_AREA_ROTATION_LANDSCAPE.left)
+    }
+
+    @Test
+    fun width_bottomRightView_ltr_basedOnDisplayHeightAndLandscapeArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(bottomRightView.layoutParams.width)
+            .isEqualTo(DISPLAY_HEIGHT - CONTENT_AREA_ROTATION_LANDSCAPE.right)
+    }
+
+    @Test
+    fun width_bottomRightView_rtl_basedOnUpsideDown() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+        configurationController.notifyLayoutDirectionChanged(isRtl = true)
+
+        assertThat(bottomRightView.layoutParams.width)
+            .isEqualTo(CONTENT_AREA_ROTATION_UPSIDE_DOWN.left)
+    }
+
+    @Test
+    fun width_bottomLeftView_ltr_basedOnDisplayWidthAndUpsideDownArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+
+        assertThat(bottomLeftView.layoutParams.width)
+            .isEqualTo(DISPLAY_WIDTH - CONTENT_AREA_ROTATION_UPSIDE_DOWN.right)
+    }
+
+    @Test
+    fun width_bottomLeftView_rtl_basedOnSeascapeArea() {
+        controller.initialize(topLeftView, topRightView, bottomLeftView, bottomRightView)
+        configurationController.notifyLayoutDirectionChanged(isRtl = true)
+
+        assertThat(bottomLeftView.layoutParams.width).isEqualTo(CONTENT_AREA_ROTATION_SEASCAPE.left)
+    }
+
+    private fun initDotView(): View =
+        View(context).also {
+            it.layoutParams = FrameLayout.LayoutParams(/* width = */ 0, /* height = */ 0)
+        }
+}
+
+private const val DISPLAY_WIDTH = 1234
+private const val DISPLAY_HEIGHT = 2345
+private val CONTENT_AREA_ROTATION_SEASCAPE = Rect(left = 10, top = 40, right = 990, bottom = 100)
+private val CONTENT_AREA_ROTATION_NONE = Rect(left = 20, top = 30, right = 980, bottom = 100)
+private val CONTENT_AREA_ROTATION_LANDSCAPE = Rect(left = 30, top = 20, right = 970, bottom = 100)
+private val CONTENT_AREA_ROTATION_UPSIDE_DOWN = Rect(left = 40, top = 10, right = 960, bottom = 100)
+
+private class InstantExecutor : DelayableExecutor {
+    override fun execute(runnable: Runnable) {
+        runnable.run()
+    }
+
+    override fun executeDelayed(runnable: Runnable, delay: Long, unit: TimeUnit) =
+        runnable.apply { run() }
+
+    override fun executeAtTime(runnable: Runnable, uptimeMillis: Long, unit: TimeUnit) =
+        runnable.apply { run() }
+}
+
+private fun Rect(left: Int, top: Int, right: Int, bottom: Int) = Rect(left, top, right, bottom)
+
+private val View.frameLayoutParams
+    get() = layoutParams as FrameLayout.LayoutParams
+
+private fun createMockDisplay() =
+    mock<Display>().also { display ->
+        whenever(display.getRealSize(any(Point::class.java))).thenAnswer { invocation ->
+            val output = invocation.arguments[0] as Point
+            output.x = DISPLAY_WIDTH
+            output.y = DISPLAY_HEIGHT
+            return@thenAnswer Unit
+        }
+        whenever(display.displayAdjustments).thenReturn(DisplayAdjustments())
+    }
+
+private fun createMockContentInsetsProvider() =
+    mock<StatusBarContentInsetsProvider>().also {
+        whenever(it.getStatusBarContentAreaForRotation(ROTATION_SEASCAPE))
+            .thenReturn(CONTENT_AREA_ROTATION_SEASCAPE)
+        whenever(it.getStatusBarContentAreaForRotation(ROTATION_NONE))
+            .thenReturn(CONTENT_AREA_ROTATION_NONE)
+        whenever(it.getStatusBarContentAreaForRotation(ROTATION_LANDSCAPE))
+            .thenReturn(CONTENT_AREA_ROTATION_LANDSCAPE)
+        whenever(it.getStatusBarContentAreaForRotation(ROTATION_UPSIDE_DOWN))
+            .thenReturn(CONTENT_AREA_ROTATION_UPSIDE_DOWN)
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
index df257ab..8be2ef0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.statusbar.events
 
 import android.content.Context
+import android.graphics.Insets
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import android.util.Pair
 import android.view.Gravity
 import android.view.View
 import android.widget.FrameLayout
@@ -79,7 +79,14 @@
         }
 
         whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(Pair(insets, insets))
+            .thenReturn(
+                Insets.of(
+                    /* left= */ insets,
+                    /* top= */ insets,
+                    /* right= */ insets,
+                    /* bottom= */ 0
+                )
+            )
         whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
             .thenReturn(portraitArea)
 
@@ -105,18 +112,18 @@
         controller.prepareChipAnimation(viewCreator)
         val chipRect = controller.chipBounds
 
-        // SB area = 10, 0, 990, 100
+        // SB area = 10, 10, 990, 100
         // chip size = 0, 0, 100, 50
-        assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75))
+        assertThat(chipRect).isEqualTo(Rect(890, 30, 990, 80))
     }
 
     @Test
     fun prepareChipAnimation_rotation_repositionsChip() {
         controller.prepareChipAnimation(viewCreator)
 
-        // Chip has been prepared, and is located at (890, 25, 990, 75)
+        // Chip has been prepared, and is located at (890, 30, 990, 75)
         // Rotation should put it into its landscape location:
-        // SB area = 10, 0, 1990, 80
+        // SB area = 10, 10, 1990, 80
         // chip size = 0, 0, 100, 50
 
         whenever(insetsProvider.getStatusBarContentAreaForCurrentRotation())
@@ -124,7 +131,7 @@
         getInsetsListener().onStatusBarContentInsetsChanged()
 
         val chipRect = controller.chipBounds
-        assertThat(chipRect).isEqualTo(Rect(1890, 15, 1990, 65))
+        assertThat(chipRect).isEqualTo(Rect(1890, 20, 1990, 70))
     }
 
     /** regression test for (b/289378932) */
@@ -162,7 +169,7 @@
 
         // THEN it still aligns the chip to the content area provided by the insets provider
         val chipRect = controller.chipBounds
-        assertThat(chipRect).isEqualTo(Rect(890, 25, 990, 75))
+        assertThat(chipRect).isEqualTo(Rect(890, 30, 990, 80))
     }
 
     private class TestView(context: Context) : View(context), BackgroundAnimatableView {
@@ -185,9 +192,9 @@
     }
 
     companion object {
-        private val portraitArea = Rect(10, 0, 990, 100)
-        private val landscapeArea = Rect(10, 0, 1990, 80)
-        private val fullScreenSb = Rect(10, 0, 990, 2000)
+        private val portraitArea = Rect(10, 10, 990, 100)
+        private val landscapeArea = Rect(10, 10, 1990, 80)
+        private val fullScreenSb = Rect(10, 10, 990, 2000)
 
         // 10px insets on both sides
         private const val insets = 10
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 5f01b5a..875fe58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.events
 
+import android.graphics.Insets
 import android.graphics.Rect
 import android.os.Process
 import android.testing.AndroidTestingRunner
@@ -91,15 +92,19 @@
 
         // StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values.
         whenever(statusBarContentInsetProvider.getStatusBarContentInsetsForCurrentRotation())
-            .thenReturn(android.util.Pair(10, 10))
+            .thenReturn(
+                Insets.of(/* left = */ 10, /* top = */ 10, /* right = */ 10, /* bottom = */ 0)
+            )
         whenever(statusBarContentInsetProvider.getStatusBarContentAreaForCurrentRotation())
-            .thenReturn(Rect(10, 0, 990, 100))
+            .thenReturn(
+                Rect(/* left = */ 10, /* top = */ 10, /* right = */ 990, /* bottom = */ 100)
+            )
 
         // StatusBarWindowController is mocked. The addViewToWindow function needs to be mocked to
         // ensure that the chip view is added to a parent view
         whenever(statusBarWindowController.addViewToWindow(any(), any())).then {
             val statusbarFake = FrameLayout(mContext)
-            statusbarFake.layout(0, 0, 1000, 100)
+            statusbarFake.layout(/* l = */ 0, /* t = */ 0, /* r = */ 1000, /* b = */ 100)
             statusbarFake.addView(
                 it.arguments[0] as View,
                 it.arguments[1] as FrameLayout.LayoutParams
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 8440e00..a5f3f57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -44,7 +44,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.WeatherData
+import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener
 import com.android.systemui.settings.UserTracker
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index e488f39..2e74d11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -34,12 +34,16 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeStateEvents;
-import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
+import com.android.systemui.shade.data.repository.ShadeRepository;
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
@@ -51,6 +55,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -62,6 +67,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.verification.VerificationMode;
 
+import kotlinx.coroutines.test.TestScope;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -75,21 +82,22 @@
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
     @Mock private HeadsUpManager mHeadsUpManager;
-    @Mock private ShadeStateEvents mShadeStateEvents;
     @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
     @Mock private VisualStabilityProvider mVisualStabilityProvider;
 
     @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
-    @Captor private ArgumentCaptor<ShadeStateEventsListener> mNotifPanelEventsCallbackCaptor;
     @Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
 
     private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
 
+    private ShadeAnimationInteractor mShadeAnimationInteractor;
+    private ShadeRepository mShadeRepository;
     private WakefulnessLifecycle.Observer mWakefulnessObserver;
     private StatusBarStateController.StateListener mStatusBarStateListener;
-    private ShadeStateEvents.ShadeStateEventsListener mNotifPanelEventsCallback;
     private NotifStabilityManager mNotifStabilityManager;
     private NotificationEntry mEntry;
     private GroupEntry mGroupEntry;
@@ -98,16 +106,19 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        mShadeRepository = new FakeShadeRepository();
+        mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl(
+                new ShadeAnimationRepository(), mShadeRepository);
         mCoordinator = new VisualStabilityCoordinator(
                 mFakeExecutor,
                 mDumpManager,
                 mHeadsUpManager,
-                mShadeStateEvents,
+                mShadeAnimationInteractor,
+                mJavaAdapter,
                 mStatusBarStateController,
                 mVisibilityLocationProvider,
                 mVisualStabilityProvider,
                 mWakefulnessLifecycle);
-
         mCoordinator.attach(mNotifPipeline);
 
         // capture arguments:
@@ -117,10 +128,6 @@
         verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
         mStatusBarStateListener = mSBStateListenerCaptor.getValue();
 
-        verify(mShadeStateEvents).addShadeStateEventsListener(
-                mNotifPanelEventsCallbackCaptor.capture());
-        mNotifPanelEventsCallback = mNotifPanelEventsCallbackCaptor.getValue();
-
         verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
         mNotifStabilityManager = mNotifStabilityManagerCaptor.getValue();
         mNotifStabilityManager.setInvalidationListener(mInvalidateListener);
@@ -545,11 +552,13 @@
     }
 
     private void setActivityLaunching(boolean activityLaunching) {
-        mNotifPanelEventsCallback.onLaunchingActivityChanged(activityLaunching);
+        mShadeAnimationInteractor.setIsLaunchingActivity(activityLaunching);
+        mTestScope.getTestScheduler().runCurrent();
     }
 
     private void setPanelCollapsing(boolean collapsing) {
-        mNotifPanelEventsCallback.onPanelCollapsingChanged(collapsing);
+        mShadeRepository.setLegacyIsClosing(collapsing);
+        mTestScope.getTestScheduler().runCurrent();
     }
 
     private void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 7415645..349a35eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -285,28 +285,13 @@
 
             assertThat(iconColors.tint).isEqualTo(0xAABBCC)
 
-            val staticDrawableColor = iconColors.staticDrawableColor(Rect(), isColorized = true)
+            val staticDrawableColor = iconColors.staticDrawableColor(Rect())
 
             assertThat(staticDrawableColor).isEqualTo(0xAABBCC)
         }
 
     @Test
-    fun iconColors_staticDrawableColor_nonColorized() =
-        testComponent.runTest {
-            darkIconRepository.darkState.value =
-                SysuiDarkIconDispatcher.DarkChange(
-                    emptyList(),
-                    0f,
-                    0xAABBCC,
-                )
-            val iconColorsLookup by collectLastValue(underTest.iconColors)
-            val iconColors = iconColorsLookup?.iconColors(Rect())
-            val staticDrawableColor = iconColors?.staticDrawableColor(Rect(), isColorized = false)
-            assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
-        }
-
-    @Test
-    fun iconColors_staticDrawableColor_isColorized_notInDarkTintArea() =
+    fun iconColors_staticDrawableColor_notInDarkTintArea() =
         testComponent.runTest {
             darkIconRepository.darkState.value =
                 SysuiDarkIconDispatcher.DarkChange(
@@ -316,8 +301,7 @@
                 )
             val iconColorsLookup by collectLastValue(underTest.iconColors)
             val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4))
-            val staticDrawableColor =
-                iconColors?.staticDrawableColor(Rect(6, 6, 7, 7), isColorized = true)
+            val staticDrawableColor = iconColors?.staticDrawableColor(Rect(6, 6, 7, 7))
             assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
index 614995b..8261c1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
@@ -40,15 +40,17 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -61,20 +63,15 @@
     val settingUri1: Uri = Secure.getUriFor(setting1)
     val settingUri2: Uri = Secure.getUriFor(setting2)
 
-    @Mock
-    private lateinit var userTracker: UserTracker
+    @Mock private lateinit var userTracker: UserTracker
     private lateinit var mainHandler: Handler
     private lateinit var backgroundHandler: Handler
     private lateinit var testableLooper: TestableLooper
-    @Mock
-    private lateinit var secureSettings: SecureSettings
-    @Mock
-    private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var secureSettings: SecureSettings
+    @Mock private lateinit var dumpManager: DumpManager
 
-    @Captor
-    private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
-    @Captor
-    private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
+    @Captor private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
+    @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
 
     private lateinit var controller: NotificationSettingsController
 
@@ -86,13 +83,13 @@
         backgroundHandler = Handler(testableLooper.looper)
         allowTestableLooperAsMainThread()
         controller =
-                NotificationSettingsController(
-                        userTracker,
-                        mainHandler,
-                        backgroundHandler,
-                        secureSettings,
-                        dumpManager
-                )
+            NotificationSettingsController(
+                userTracker,
+                mainHandler,
+                backgroundHandler,
+                secureSettings,
+                dumpManager
+            )
     }
 
     @After
@@ -116,14 +113,13 @@
 
         // Validate: Nothing to do, since we aren't monitoring settings
         verify(secureSettings, never()).unregisterContentObserver(any())
-        verify(secureSettings, never()).registerContentObserverForUser(
-                any(Uri::class.java), anyBoolean(), any(), anyInt())
+        verify(secureSettings, never())
+            .registerContentObserverForUser(any(Uri::class.java), anyBoolean(), any(), anyInt())
     }
     @Test
     fun updateContentObserverRegistration_onUserChange_withSettingsListeners() {
         // When: someone is listening to a setting
-        controller.addCallback(settingUri1,
-                Mockito.mock(Listener::class.java))
+        controller.addCallback(settingUri1, Mockito.mock(Listener::class.java))
 
         verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
         val userCallback = userTrackerCallbackCaptor.value
@@ -134,103 +130,141 @@
 
         // Validate: The tracker is unregistered and re-registered with the new user
         verify(secureSettings).unregisterContentObserver(any())
-        verify(secureSettings).registerContentObserverForUser(
-                eq(settingUri1), eq(false), any(), eq(userId))
+        verify(secureSettings)
+            .registerContentObserverForUser(eq(settingUri1), eq(false), any(), eq(userId))
     }
 
     @Test
     fun addCallback_onlyFirstForUriRegistersObserver() {
-        controller.addCallback(settingUri1,
-                Mockito.mock(Listener::class.java))
-        verify(secureSettings).registerContentObserverForUser(
-                eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+        controller.addCallback(settingUri1, Mockito.mock(Listener::class.java))
+        verifyZeroInteractions(secureSettings)
+        testableLooper.processAllMessages()
+        verify(secureSettings)
+            .registerContentObserverForUser(
+                eq(settingUri1),
+                eq(false),
+                any(),
+                eq(ActivityManager.getCurrentUser())
+            )
 
-        controller.addCallback(settingUri1,
-                Mockito.mock(Listener::class.java))
-        verify(secureSettings).registerContentObserverForUser(
-                any(Uri::class.java), anyBoolean(), any(), anyInt())
+        controller.addCallback(settingUri1, Mockito.mock(Listener::class.java))
+        verify(secureSettings)
+            .registerContentObserverForUser(any(Uri::class.java), anyBoolean(), any(), anyInt())
     }
 
     @Test
     fun addCallback_secondUriRegistersObserver() {
-        controller.addCallback(settingUri1,
-                Mockito.mock(Listener::class.java))
-        verify(secureSettings).registerContentObserverForUser(
-                eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+        controller.addCallback(settingUri1, Mockito.mock(Listener::class.java))
+        verifyZeroInteractions(secureSettings)
+        testableLooper.processAllMessages()
+        verify(secureSettings)
+            .registerContentObserverForUser(
+                eq(settingUri1),
+                eq(false),
+                any(),
+                eq(ActivityManager.getCurrentUser())
+            )
+        clearInvocations(secureSettings)
 
-        controller.addCallback(settingUri2,
-                Mockito.mock(Listener::class.java))
-        verify(secureSettings).registerContentObserverForUser(
-                eq(settingUri2), eq(false), any(), eq(ActivityManager.getCurrentUser()))
-        verify(secureSettings).registerContentObserverForUser(
-                eq(settingUri1), anyBoolean(), any(), anyInt())
+        controller.addCallback(settingUri2, Mockito.mock(Listener::class.java))
+        verifyNoMoreInteractions(secureSettings)
+        testableLooper.processAllMessages()
+        verify(secureSettings)
+            .registerContentObserverForUser(
+                eq(settingUri2),
+                eq(false),
+                any(),
+                eq(ActivityManager.getCurrentUser())
+            )
     }
 
     @Test
     fun removeCallback_lastUnregistersObserver() {
-        val listenerSetting1 : Listener = mock()
-        val listenerSetting2 : Listener = mock()
+        val listenerSetting1: Listener = mock()
+        val listenerSetting2: Listener = mock()
         controller.addCallback(settingUri1, listenerSetting1)
-        verify(secureSettings).registerContentObserverForUser(
-                eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+        verifyZeroInteractions(secureSettings)
+        testableLooper.processAllMessages()
+        verify(secureSettings)
+            .registerContentObserverForUser(
+                eq(settingUri1),
+                eq(false),
+                any(),
+                eq(ActivityManager.getCurrentUser())
+            )
+        clearInvocations(secureSettings)
 
         controller.addCallback(settingUri2, listenerSetting2)
-        verify(secureSettings).registerContentObserverForUser(
-                eq(settingUri2), anyBoolean(), any(), anyInt())
+        verifyNoMoreInteractions(secureSettings)
+        testableLooper.processAllMessages()
+        verify(secureSettings)
+            .registerContentObserverForUser(eq(settingUri2), anyBoolean(), any(), anyInt())
+        clearInvocations(secureSettings)
 
         controller.removeCallback(settingUri2, listenerSetting2)
+        testableLooper.processAllMessages()
         verify(secureSettings, never()).unregisterContentObserver(any())
+        clearInvocations(secureSettings)
 
         controller.removeCallback(settingUri1, listenerSetting1)
+        verifyNoMoreInteractions(secureSettings)
+        testableLooper.processAllMessages()
         verify(secureSettings).unregisterContentObserver(any())
     }
 
     @Test
     fun addCallback_updatesCurrentValue() {
-        whenever(secureSettings.getStringForUser(
-                setting1, ActivityManager.getCurrentUser())).thenReturn("9")
-        whenever(secureSettings.getStringForUser(
-                setting2, ActivityManager.getCurrentUser())).thenReturn("5")
+        whenever(secureSettings.getStringForUser(setting1, ActivityManager.getCurrentUser()))
+            .thenReturn("9")
+        whenever(secureSettings.getStringForUser(setting2, ActivityManager.getCurrentUser()))
+            .thenReturn("5")
 
-        val listenerSetting1a : Listener = mock()
-        val listenerSetting1b : Listener = mock()
-        val listenerSetting2 : Listener = mock()
+        val listenerSetting1a: Listener = mock()
+        val listenerSetting1b: Listener = mock()
+        val listenerSetting2: Listener = mock()
 
         controller.addCallback(settingUri1, listenerSetting1a)
         controller.addCallback(settingUri1, listenerSetting1b)
         controller.addCallback(settingUri2, listenerSetting2)
 
+        verifyZeroInteractions(secureSettings)
         testableLooper.processAllMessages()
 
-        verify(listenerSetting1a).onSettingChanged(
-                settingUri1, ActivityManager.getCurrentUser(), "9")
-        verify(listenerSetting1b).onSettingChanged(
-                settingUri1, ActivityManager.getCurrentUser(), "9")
-        verify(listenerSetting2).onSettingChanged(
-                settingUri2, ActivityManager.getCurrentUser(), "5")
+        verify(listenerSetting1a)
+            .onSettingChanged(settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1b)
+            .onSettingChanged(settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting2)
+            .onSettingChanged(settingUri2, ActivityManager.getCurrentUser(), "5")
     }
 
     @Test
     fun removeCallback_noMoreUpdates() {
-        whenever(secureSettings.getStringForUser(
-                setting1, ActivityManager.getCurrentUser())).thenReturn("9")
+        whenever(secureSettings.getStringForUser(setting1, ActivityManager.getCurrentUser()))
+            .thenReturn("9")
 
-        val listenerSetting1a : Listener = mock()
-        val listenerSetting1b : Listener = mock()
+        val listenerSetting1a: Listener = mock()
+        val listenerSetting1b: Listener = mock()
 
         // First, register
         controller.addCallback(settingUri1, listenerSetting1a)
         controller.addCallback(settingUri1, listenerSetting1b)
+        verifyZeroInteractions(secureSettings)
         testableLooper.processAllMessages()
 
-        verify(secureSettings).registerContentObserverForUser(
-                any(Uri::class.java), anyBoolean(), capture(settingsObserverCaptor), anyInt())
-        verify(listenerSetting1a).onSettingChanged(
-                settingUri1, ActivityManager.getCurrentUser(), "9")
-        verify(listenerSetting1b).onSettingChanged(
-                settingUri1, ActivityManager.getCurrentUser(), "9")
-        Mockito.clearInvocations(listenerSetting1b)
-        Mockito.clearInvocations(listenerSetting1a)
+        verify(secureSettings)
+            .registerContentObserverForUser(
+                any(Uri::class.java),
+                anyBoolean(),
+                capture(settingsObserverCaptor),
+                anyInt()
+            )
+        verify(listenerSetting1a)
+            .onSettingChanged(settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1b)
+            .onSettingChanged(settingUri1, ActivityManager.getCurrentUser(), "9")
+        clearInvocations(listenerSetting1b)
+        clearInvocations(listenerSetting1a)
 
         // Remove one of them
         controller.removeCallback(settingUri1, listenerSetting1a)
@@ -239,10 +273,9 @@
         settingsObserverCaptor.value.onChange(false, settingUri1)
         testableLooper.processAllMessages()
 
-        verify(listenerSetting1a, never()).onSettingChanged(
-                settingUri1, ActivityManager.getCurrentUser(), "9")
-        verify(listenerSetting1b).onSettingChanged(
-                settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1a, never())
+            .onSettingChanged(settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1b)
+            .onSettingChanged(settingUri1, ActivityManager.getCurrentUser(), "9")
     }
-
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 7de05ad..00a86ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -34,6 +34,9 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -86,6 +89,8 @@
     @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
     private lateinit var underTest: ActivityStarterImpl
     private val mainExecutor = FakeExecutor(FakeSystemClock())
+    private val shadeAnimationInteractor =
+        ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), FakeShadeRepository())
 
     @Before
     fun setUp() {
@@ -99,6 +104,7 @@
                 Lazy { keyguardViewMediator },
                 Lazy { shadeController },
                 Lazy { shadeViewController },
+                shadeAnimationInteractor,
                 Lazy { statusBarKeyguardViewManager },
                 Lazy { notifShadeWindowController },
                 activityLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 9c10131..65d71f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -16,33 +16,47 @@
 
 package com.android.systemui.statusbar.phone
 
+import android.content.res.Configuration
+import android.graphics.Insets
+import android.graphics.Rect
+import android.testing.TestableLooper.RunWithLooper
+import android.view.DisplayCutout
+import android.view.DisplayShape
+import android.view.LayoutInflater
 import android.view.MotionEvent
-import android.view.ViewGroup
+import android.view.PrivacyIndicatorBounds
+import android.view.RoundedCorners
+import android.view.WindowInsets
+import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.spy
 
 @SmallTest
+@RunWithLooper(setAsMainLooper = true)
 class PhoneStatusBarViewTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var shadeViewController: ShadeViewController
-    @Mock
-    private lateinit var panelView: ViewGroup
-
     private lateinit var view: PhoneStatusBarView
 
+    private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>()
+
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        view = PhoneStatusBarView(mContext, null)
+        mDependency.injectTestDependency(
+            StatusBarContentInsetsProvider::class.java,
+            contentInsetsProvider
+        )
+        mDependency.injectTestDependency(DarkIconDispatcher::class.java, mock<DarkIconDispatcher>())
+        view = spy(createStatusBarView())
+        whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets())
     }
 
     @Test
@@ -95,6 +109,48 @@
         // No assert needed, just testing no crash
     }
 
+    @Test
+    fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
+        val insets = Insets.of(/* left = */ 10, /* top = */ 20, /* right = */ 30, /* bottom = */ 40)
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(insets)
+
+        view.onAttachedToWindow()
+
+        assertThat(view.paddingLeft).isEqualTo(insets.left)
+        assertThat(view.paddingTop).isEqualTo(insets.top)
+        assertThat(view.paddingRight).isEqualTo(insets.right)
+        assertThat(view.paddingBottom).isEqualTo(0)
+    }
+
+    @Test
+    fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() {
+        val insets = Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10)
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(insets)
+
+        view.onConfigurationChanged(Configuration())
+
+        assertThat(view.paddingLeft).isEqualTo(insets.left)
+        assertThat(view.paddingTop).isEqualTo(insets.top)
+        assertThat(view.paddingRight).isEqualTo(insets.right)
+        assertThat(view.paddingBottom).isEqualTo(0)
+    }
+
+    @Test
+    fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() {
+        val insets = Insets.of(/* left = */ 90, /* top = */ 10, /* right = */ 45, /* bottom = */ 50)
+        whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
+            .thenReturn(insets)
+
+        view.onApplyWindowInsets(WindowInsets(Rect()))
+
+        assertThat(view.paddingLeft).isEqualTo(insets.left)
+        assertThat(view.paddingTop).isEqualTo(insets.top)
+        assertThat(view.paddingRight).isEqualTo(insets.right)
+        assertThat(view.paddingBottom).isEqualTo(0)
+    }
+
     private class TestTouchEventHandler : Gefingerpoken {
         var lastInterceptEvent: MotionEvent? = null
         var lastEvent: MotionEvent? = null
@@ -110,4 +166,28 @@
             return handleTouchReturnValue
         }
     }
+
+    private fun createStatusBarView() =
+        LayoutInflater.from(context)
+            .inflate(
+                R.layout.status_bar,
+                /* root= */ FrameLayout(context),
+                /* attachToRoot = */ false
+            ) as PhoneStatusBarView
+
+    private fun emptyWindowInsets() =
+        WindowInsets(
+            /* typeInsetsMap = */ arrayOf(),
+            /* typeMaxInsetsMap = */ arrayOf(),
+            /* typeVisibilityMap = */ booleanArrayOf(),
+            /* isRound = */ false,
+            /* forceConsumingTypes = */ 0,
+            /* suppressScrimTypes = */ 0,
+            /* displayCutout = */ DisplayCutout.NO_CUTOUT,
+            /* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
+            /* privacyIndicatorBounds = */ PrivacyIndicatorBounds(),
+            /* displayShape = */ DisplayShape.NONE,
+            /* compatInsetsTypes = */ 0,
+            /* compatIgnoreVisibility = */ false
+        )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 210c5ab..5c56246 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
 import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
 import com.android.systemui.util.leak.RotationUtils.Rotation
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertTrue
 import org.junit.Before
@@ -62,7 +63,6 @@
         `when`(contextMock.createConfigurationContext(any())).thenAnswer {
             context.createConfigurationContext(it.arguments[0] as Configuration)
         }
-
         configurationController = ConfigurationControllerImpl(contextMock)
     }
 
@@ -76,6 +76,7 @@
         val currentRotation = ROTATION_NONE
         val chipWidth = 30
         val dotWidth = 10
+        val statusBarContentHeight = 15
 
         var isRtl = false
         var targetRotation = ROTATION_NONE
@@ -88,7 +89,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
         /* 1080 - 20 (rounded corner) - 30 (chip),
@@ -119,7 +122,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
         /* 2160 - 20 (rounded corner) - 30 (chip),
@@ -141,6 +146,20 @@
     }
 
     @Test
+    fun privacyChipBoundingRectForInsets_usesTopInset() {
+        val chipWidth = 30
+        val dotWidth = 10
+        val isRtl = false
+        val contentRect =
+                Rect(/* left = */ 0, /* top = */ 10, /* right = */ 1000, /* bottom = */ 100)
+
+        val chipBounds =
+                getPrivacyChipBoundingRectForInsets(contentRect, dotWidth, chipWidth, isRtl)
+
+        assertThat(chipBounds.top).isEqualTo(contentRect.top)
+    }
+
+    @Test
     fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() {
         // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
         val screenBounds = Rect(0, 0, 1080, 2160)
@@ -152,6 +171,7 @@
         val currentRotation = ROTATION_NONE
         val isRtl = false
         val dotWidth = 10
+        val statusBarContentHeight = 15
 
         `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
 
@@ -172,7 +192,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -191,7 +213,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -212,7 +236,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -232,12 +258,60 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
 
     @Test
+    fun calculateInsetsForRotationWithRotatedResources_bottomAlignedMarginDisabled_noTopInset() {
+        whenever(dc.boundingRects).thenReturn(emptyList())
+
+        val bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation = ROTATION_NONE,
+                targetRotation = ROTATION_NONE,
+                displayCutout = dc,
+                maxBounds = Rect(0, 0, 1080, 2160),
+                statusBarHeight = 100,
+                minLeft = 0,
+                minRight = 0,
+                isRtl = false,
+                dotWidth = 10,
+                bottomAlignedMargin = BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight = 15)
+
+        assertThat(bounds.top).isEqualTo(0)
+    }
+
+    @Test
+    fun calculateInsetsForRotationWithRotatedResources_bottomAlignedMargin_topBasedOnMargin() {
+        whenever(dc.boundingRects).thenReturn(emptyList())
+
+        val bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation = ROTATION_NONE,
+                targetRotation = ROTATION_NONE,
+                displayCutout = dc,
+                maxBounds = Rect(0, 0, 1080, 2160),
+                statusBarHeight = 100,
+                minLeft = 0,
+                minRight = 0,
+                isRtl = false,
+                dotWidth = 10,
+                bottomAlignedMargin = 5,
+                statusBarContentHeight = 15)
+
+        // Content in the status bar is centered vertically. To achieve the bottom margin we want,
+        // we need to "shrink" the height of the status bar until the centered content has the
+        // desired bottom margin. To achieve this shrinking, we use top inset/padding.
+        // "New" SB height = bottom margin * 2 + content height
+        // Top inset = SB height - "New" SB height
+        val expectedTopInset = 75
+        assertThat(bounds.top).isEqualTo(expectedTopInset)
+    }
+
+    @Test
     fun testCalculateInsetsForRotationWithRotatedResources_nonCornerCutout() {
         // GIVEN phone in portrait mode, where width < height and the cutout is not in the corner
         // the assumption here is that if the cutout does NOT touch the corner then we have room to
@@ -253,6 +327,7 @@
         val currentRotation = ROTATION_NONE
         val isRtl = false
         val dotWidth = 10
+        val statusBarContentHeight = 15
 
         `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
 
@@ -273,7 +348,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -292,7 +369,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -311,7 +390,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
@@ -330,7 +411,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
@@ -346,6 +429,7 @@
         val sbHeightLandscape = 60
         val isRtl = false
         val dotWidth = 10
+        val statusBarContentHeight = 15
 
         // THEN content insets should only use rounded corner padding
         var targetRotation = ROTATION_NONE
@@ -363,7 +447,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_LANDSCAPE
@@ -381,7 +467,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_UPSIDE_DOWN
@@ -399,7 +487,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_LANDSCAPE
@@ -417,7 +507,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
 
@@ -433,17 +525,18 @@
         val currentRotation = ROTATION_NONE
         val isRtl = false
         val dotWidth = 10
+        val statusBarContentHeight = 15
 
         `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
 
         // THEN left should be set to the display cutout width, and right should use the minRight
-        var targetRotation = ROTATION_NONE
-        var expectedBounds = Rect(dcBounds.right,
+        val targetRotation = ROTATION_NONE
+        val expectedBounds = Rect(dcBounds.right,
                 0,
                 screenBounds.right - minRightPadding,
                 sbHeightPortrait)
 
-        var bounds = calculateInsetsForRotationWithRotatedResources(
+        val bounds = calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
                 dc,
@@ -452,7 +545,9 @@
                 minLeftPadding,
                 minRightPadding,
                 isRtl,
-                dotWidth)
+                dotWidth,
+                BOTTOM_ALIGNED_MARGIN_NONE,
+                statusBarContentHeight)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
@@ -577,4 +672,8 @@
                 " expected=$expected actual=$actual",
                 expected.equals(actual))
     }
+
+    companion object {
+        private const val BOTTOM_ALIGNED_MARGIN_NONE = -1
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 6cc4e44..592c78f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -76,6 +76,9 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
+import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
+import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl;
 import com.android.systemui.statusbar.NotificationClickNotifier;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -253,10 +256,11 @@
                         mock(ShadeViewController.class),
                         mock(NotificationShadeWindowController.class),
                         mActivityLaunchAnimator,
+                        new ShadeAnimationInteractorLegacyImpl(
+                                new ShadeAnimationRepository(), new FakeShadeRepository()),
                         notificationAnimationProvider,
                         mock(LaunchFullScreenIntentProvider.class),
                         mPowerInteractor,
-                        mFeatureFlags,
                         mUserTracker
                 );
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index 21936c3..85a233fd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -20,7 +20,7 @@
 import com.android.keyguard.KeyguardClockSwitch.ClockSize
 import com.android.keyguard.KeyguardClockSwitch.LARGE
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
-import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
 import com.android.systemui.util.mockito.mock
 import dagger.Binds
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/alarm/AlarmTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/alarm/AlarmTileKosmos.kt
new file mode 100644
index 0000000..2fa92c7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/alarm/AlarmTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.alarm
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.policy.PolicyModule
+
+val Kosmos.qsAlarmTileConfig by
+    Kosmos.Fixture { PolicyModule.provideAlarmTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
new file mode 100644
index 0000000..9d0faca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom
+
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.states
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Subject.Factory
+import com.google.common.truth.Truth
+
+/**
+ * [QSTileState]-specific extension for [Truth]. Use [assertThat] or [states] to get an instance of
+ * this subject.
+ */
+class QSTileStateSubject
+private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) :
+    Subject(failureMetadata, subject) {
+
+    private val actual: QSTileState? = subject
+
+    /** Asserts if the [QSTileState] fields are the same. */
+    fun isEqualTo(other: QSTileState?) {
+        if (actual == null) {
+            check("other").that(other).isNull()
+            return
+        } else {
+            check("other").that(other).isNotNull()
+            other ?: return
+        }
+        check("icon").that(actual.icon()).isEqualTo(other.icon())
+        check("label").that(actual.label).isEqualTo(other.label)
+        check("activationState").that(actual.activationState).isEqualTo(other.activationState)
+        check("secondaryLabel").that(actual.secondaryLabel).isEqualTo(other.secondaryLabel)
+        check("label").that(actual.supportedActions).isEqualTo(other.supportedActions)
+        check("contentDescription")
+            .that(actual.contentDescription)
+            .isEqualTo(other.contentDescription)
+        check("stateDescription").that(actual.stateDescription).isEqualTo(other.stateDescription)
+        check("sideViewIcon").that(actual.sideViewIcon).isEqualTo(other.sideViewIcon)
+        check("enabledState").that(actual.enabledState).isEqualTo(other.enabledState)
+        check("expandedAccessibilityClassName")
+            .that(actual.expandedAccessibilityClassName)
+            .isEqualTo(other.expandedAccessibilityClassName)
+    }
+
+    companion object {
+
+        /** Returns a factory to be used with [Truth.assertAbout]. */
+        fun states(): Factory<QSTileStateSubject, QSTileState?> {
+            return Factory { failureMetadata: FailureMetadata, subject: QSTileState? ->
+                QSTileStateSubject(failureMetadata, subject)
+            }
+        }
+
+        /** Shortcut for `Truth.assertAbout(states()).that(state)`. */
+        fun assertThat(state: QSTileState?): QSTileStateSubject =
+            Truth.assertAbout(states()).that(state)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index a70b91d..9c10848 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -106,6 +106,14 @@
         _legacyQsFullscreen.value = legacyQsFullscreen
     }
 
+    private val _legacyIsClosing = MutableStateFlow(false)
+    @Deprecated("Use ShadeInteractor instead") override val legacyIsClosing = _legacyIsClosing
+
+    @Deprecated("Use ShadeInteractor instead")
+    override fun setLegacyIsClosing(isClosing: Boolean) {
+        _legacyIsClosing.value = isClosing
+    }
+
     fun setShadeModel(model: ShadeModel) {
         _shadeModel.value = model
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
index 50d3f0a..282e2e8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
@@ -24,7 +24,7 @@
 
 @SysUISingleton
 class FakeDarkIconRepository @Inject constructor() : DarkIconRepository {
-    override val darkState = MutableStateFlow(DarkChange(emptyList(), 0f, 0))
+    override val darkState = MutableStateFlow(DarkChange.EMPTY)
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
index 23477d8..c51de33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
@@ -11,6 +11,7 @@
 class FakeConfigurationController @Inject constructor() : ConfigurationController {
 
     private var listeners = mutableListOf<ConfigurationController.ConfigurationListener>()
+    private var isRtl = false
 
     override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
         listeners += listener
@@ -36,7 +37,12 @@
         onConfigurationChanged(newConfiguration = null)
     }
 
-    override fun isLayoutRtl(): Boolean = false
+    fun notifyLayoutDirectionChanged(isRtl: Boolean) {
+        this.isRtl = isRtl
+        listeners.forEach { it.onLayoutDirectionChanged(isRtl) }
+    }
+
+    override fun isLayoutRtl(): Boolean = isRtl
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
index 5ae8e22..377e97c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
@@ -14,15 +14,44 @@
 
 package com.android.systemui.utils.leaks;
 
+import android.app.AlarmManager;
 import android.testing.LeakCheck;
 
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class FakeNextAlarmController extends BaseLeakChecker<NextAlarmChangeCallback>
         implements NextAlarmController {
 
+    private AlarmManager.AlarmClockInfo mNextAlarm = null;
+    private List<NextAlarmChangeCallback> mCallbacks = new ArrayList<>();
+
     public FakeNextAlarmController(LeakCheck test) {
         super(test, "alarm");
     }
+
+    /**
+     * Helper method for setting the next alarm
+     */
+    public void setNextAlarm(AlarmManager.AlarmClockInfo nextAlarm) {
+        this.mNextAlarm = nextAlarm;
+        for (var callback: mCallbacks) {
+            callback.onNextAlarmChanged(nextAlarm);
+        }
+    }
+
+    @Override
+    public void addCallback(NextAlarmChangeCallback listener) {
+        mCallbacks.add(listener);
+        listener.onNextAlarmChanged(mNextAlarm);
+    }
+
+    @Override
+    public void removeCallback(NextAlarmChangeCallback listener) {
+        mCallbacks.remove(listener);
+    }
+
 }
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 96cfa48..6a6ae38 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -85,18 +85,13 @@
 class com.android.internal.util.FastPrintWriter stubclass
 class com.android.internal.util.GrowingArrayUtils stubclass
 class com.android.internal.util.LineBreakBufferedWriter stubclass
+class com.android.internal.util.Parcelling stubclass
 class com.android.internal.util.Preconditions stubclass
 class com.android.internal.util.StringPool stubclass
 
 class com.android.internal.os.SomeArgs stubclass
 
 # Parcel
-class android.os.Parcel stubclass
-    method writeException (Ljava/lang/Exception;)V @writeException$ravenwood
-    method writeNoException ()V @writeNoException$ravenwood
-class android.os.Parcel !com.android.hoststubgen.nativesubstitution.Parcel_host
-
-class android.os.Parcelable stubclass
 class android.os.ParcelFormatException stubclass
 class android.os.BadParcelableException stubclass
 class android.os.BadTypeParcelableException stubclass
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 72e9ba3..2902932 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -23,6 +23,8 @@
 android.os.Looper
 android.os.Message
 android.os.MessageQueue
+android.os.Parcel
+android.os.Parcelable
 android.os.Process
 android.os.SystemClock
 android.os.ThreadLocalWorkSource
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2eecb4d..5bffe80 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -24,7 +24,7 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROADCAST_RECEIVER;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
 import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
@@ -135,7 +135,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 import android.view.accessibility.IAccessibilityManager;
 import android.view.accessibility.IAccessibilityManagerClient;
-import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.inputmethod.EditorInfo;
 
 import com.android.internal.R;
@@ -3431,7 +3431,7 @@
         }
     }
 
-    private void updateWindowMagnificationConnectionIfNeeded(AccessibilityUserState userState) {
+    private void updateMagnificationConnectionIfNeeded(AccessibilityUserState userState) {
         if (!mMagnificationController.supportWindowMagnification()) {
             return;
         }
@@ -4110,12 +4110,12 @@
     }
 
     @Override
-    public void setWindowMagnificationConnection(
-            IWindowMagnificationConnection connection) throws RemoteException {
+    public void setMagnificationConnection(
+            IMagnificationConnection connection) throws RemoteException {
         if (mTraceManager.isA11yTracingEnabledForTypes(
-                FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
-            mTraceManager.logTrace(LOG_TAG + ".setWindowMagnificationConnection",
-                    FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+                FLAGS_ACCESSIBILITY_MANAGER | FLAGS_MAGNIFICATION_CONNECTION)) {
+            mTraceManager.logTrace(LOG_TAG + ".setMagnificationConnection",
+                    FLAGS_ACCESSIBILITY_MANAGER | FLAGS_MAGNIFICATION_CONNECTION,
                     "connection=" + connection);
         }
 
@@ -4422,7 +4422,7 @@
                 pw.append("visibleBgUserIds=").append(mVisibleBgUserIds.toString());
                 pw.println();
             }
-            pw.append("hasWindowMagnificationConnection=").append(
+            pw.append("hasMagnificationConnection=").append(
                     String.valueOf(getMagnificationConnectionManager().isConnected()));
             pw.println();
             mMagnificationProcessor.dump(pw, getValidDisplayList());
@@ -5132,7 +5132,7 @@
                 updateMagnificationModeChangeSettingsLocked(userState, displayId);
             }
         }
-        updateWindowMagnificationConnectionIfNeeded(userState);
+        updateMagnificationConnectionIfNeeded(userState);
         // Remove magnification button UI when the magnification capability is not all mode or
         // magnification is disabled.
         if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked()
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
index 6114213..307b555 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
@@ -223,7 +223,7 @@
         pw.println("            IAccessibilityInteractionConnection");
         pw.println("            IAccessibilityInteractionConnectionCallback");
         pw.println("            IRemoteMagnificationAnimationCallback");
-        pw.println("            IWindowMagnificationConnection");
+        pw.println("            IMagnificationConnection");
         pw.println("            IWindowMagnificationConnectionCallback");
         pw.println("            WindowManagerInternal");
         pw.println("            WindowsForAccessibilityCallback");
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
index 5a3c070..eff6488 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
@@ -16,7 +16,7 @@
 
 package com.android.server.accessibility.magnification;
 
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK;
 import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK;
 
@@ -42,7 +42,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.MotionEvent;
-import android.view.accessibility.IWindowMagnificationConnection;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
@@ -61,8 +61,8 @@
 
 /**
  * A class to manipulate magnification through {@link MagnificationConnectionWrapper}
- * create by {@link #setConnection(IWindowMagnificationConnection)}. To set the connection with
- * SysUI, call {@code StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)}.
+ * create by {@link #setConnection(IMagnificationConnection)}. To set the connection with
+ * SysUI, call {@code StatusBarManagerInternal#requestMagnificationConnection(boolean)}.
  * The applied magnification scale is constrained by
  * {@link MagnificationScaleProvider#constrainScale(float)}
  */
@@ -93,13 +93,13 @@
     })
     public @interface WindowPosition {}
 
-    /** Window magnification connection is connecting. */
+    /** Magnification connection is connecting. */
     private static final int CONNECTING = 0;
-    /** Window magnification connection is connected. */
+    /** Magnification connection is connected. */
     private static final int CONNECTED = 1;
-    /** Window magnification connection is disconnecting. */
+    /** Magnification connection is disconnecting. */
     private static final int DISCONNECTING = 2;
-    /** Window magnification connection is disconnected. */
+    /** Magnification connection is disconnected. */
     private static final int DISCONNECTED = 3;
 
     @Retention(RetentionPolicy.SOURCE)
@@ -195,7 +195,7 @@
         void onSourceBoundsChanged(int displayId, Rect bounds);
 
         /**
-         * Called from {@link IWindowMagnificationConnection} to request changing the magnification
+         * Called from {@link IMagnificationConnection} to request changing the magnification
          * mode on the given display.
          *
          * @param displayId the logical display id
@@ -218,11 +218,11 @@
     }
 
     /**
-     * Sets {@link IWindowMagnificationConnection}.
+     * Sets {@link IMagnificationConnection}.
      *
-     * @param connection {@link IWindowMagnificationConnection}
+     * @param connection {@link IMagnificationConnection}
      */
-    public void setConnection(@Nullable IWindowMagnificationConnection connection) {
+    public void setConnection(@Nullable IMagnificationConnection connection) {
         if (DBG) {
             Slog.d(TAG, "setConnection :" + connection + ", mConnectionState="
                     + connectionStateToString(mConnectionState));
@@ -266,7 +266,7 @@
     }
 
     /**
-     * @return {@code true} if {@link IWindowMagnificationConnection} is available
+     * @return {@code true} if {@link IMagnificationConnection} is available
      */
     public boolean isConnected() {
         synchronized (mLock) {
@@ -275,21 +275,21 @@
     }
 
     /**
-     * Requests {@link IWindowMagnificationConnection} through
-     * {@link StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)} and
+     * Requests {@link IMagnificationConnection} through
+     * {@link StatusBarManagerInternal#requestMagnificationConnection(boolean)} and
      * destroys all window magnifications if necessary.
      *
      * @param connect {@code true} if needs connection, otherwise set the connection to null and
      *                destroy all window magnifications.
-     * @return {@code true} if {@link IWindowMagnificationConnection} state is going to change.
+     * @return {@code true} if {@link IMagnificationConnection} state is going to change.
      */
     public boolean requestConnection(boolean connect) {
         if (DBG) {
             Slog.d(TAG, "requestConnection :" + connect);
         }
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
-            mTrace.logTrace(TAG + ".requestWindowMagnificationConnection",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect);
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".requestMagnificationConnection",
+                    FLAGS_MAGNIFICATION_CONNECTION, "connect=" + connect);
         }
         synchronized (mLock) {
             if ((connect && (mConnectionState == CONNECTED || mConnectionState == CONNECTING))
@@ -329,7 +329,7 @@
             final StatusBarManagerInternal service = LocalServices.getService(
                     StatusBarManagerInternal.class);
             if (service != null) {
-                return service.requestWindowMagnificationConnection(connect);
+                return service.requestMagnificationConnection(connect);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
index 20538f1..d7098a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
@@ -16,8 +16,8 @@
 
 package com.android.server.accessibility.magnification;
 
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK;
-import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK;
 import static android.os.IBinder.DeathRecipient;
 
@@ -25,25 +25,25 @@
 import android.annotation.Nullable;
 import android.os.RemoteException;
 import android.util.Slog;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
 import com.android.server.accessibility.AccessibilityTraceManager;
 
 /**
- * A wrapper of {@link IWindowMagnificationConnection}.
+ * A wrapper of {@link IMagnificationConnection}.
  */
 class MagnificationConnectionWrapper {
 
     private static final boolean DBG = false;
     private static final String TAG = "MagnificationConnectionWrapper";
 
-    private final @NonNull IWindowMagnificationConnection mConnection;
+    private final @NonNull IMagnificationConnection mConnection;
     private final @NonNull AccessibilityTraceManager mTrace;
 
-    MagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection,
+    MagnificationConnectionWrapper(@NonNull IMagnificationConnection connection,
             @NonNull AccessibilityTraceManager trace) {
         mConnection = connection;
         mTrace = trace;
@@ -61,9 +61,9 @@
     boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
             float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
             @Nullable MagnificationAnimationCallback callback) {
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
             mTrace.logTrace(TAG + ".enableWindowMagnification",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+                    FLAGS_MAGNIFICATION_CONNECTION,
                     "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX
                             + ";centerY=" + centerY + ";magnificationFrameOffsetRatioX="
                             + magnificationFrameOffsetRatioX + ";magnificationFrameOffsetRatioY="
@@ -83,8 +83,8 @@
     }
 
     boolean setScaleForWindowMagnification(int displayId, float scale) {
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
-            mTrace.logTrace(TAG + ".setScale", FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".setScale", FLAGS_MAGNIFICATION_CONNECTION,
                     "displayId=" + displayId + ";scale=" + scale);
         }
         try {
@@ -100,9 +100,9 @@
 
     boolean disableWindowMagnification(int displayId,
             @Nullable MagnificationAnimationCallback callback) {
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
             mTrace.logTrace(TAG + ".disableWindowMagnification",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+                    FLAGS_MAGNIFICATION_CONNECTION,
                     "displayId=" + displayId + ";callback=" + callback);
         }
         try {
@@ -118,8 +118,8 @@
     }
 
     boolean moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
-            mTrace.logTrace(TAG + ".moveWindowMagnifier", FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
+            mTrace.logTrace(TAG + ".moveWindowMagnifier", FLAGS_MAGNIFICATION_CONNECTION,
                     "displayId=" + displayId + ";offsetX=" + offsetX + ";offsetY=" + offsetY);
         }
         try {
@@ -135,9 +135,9 @@
 
     boolean moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
             @Nullable MagnificationAnimationCallback callback) {
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
             mTrace.logTrace(TAG + ".moveWindowMagnifierToPosition",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId
+                    FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId
                             + ";positionX=" + positionX + ";positionY=" + positionY);
         }
         try {
@@ -153,9 +153,9 @@
     }
 
     boolean showMagnificationButton(int displayId, int magnificationMode) {
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
             mTrace.logTrace(TAG + ".showMagnificationButton",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+                    FLAGS_MAGNIFICATION_CONNECTION,
                     "displayId=" + displayId + ";mode=" + magnificationMode);
         }
         try {
@@ -170,9 +170,9 @@
     }
 
     boolean removeMagnificationButton(int displayId) {
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
             mTrace.logTrace(TAG + ".removeMagnificationButton",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
+                    FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
         }
         try {
             mConnection.removeMagnificationButton(displayId);
@@ -186,9 +186,9 @@
     }
 
     boolean removeMagnificationSettingsPanel(int displayId) {
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
             mTrace.logTrace(TAG + ".removeMagnificationSettingsPanel",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
+                    FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
         }
         try {
             mConnection.removeMagnificationSettingsPanel(displayId);
@@ -202,9 +202,9 @@
     }
 
     boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
-        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+        if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
             mTrace.logTrace(TAG + ".onMagnificationScaleUpdated",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
+                    FLAGS_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
         }
         try {
             mConnection.onUserMagnificationScaleChanged(userId, displayId, scale);
@@ -219,10 +219,10 @@
 
     boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) {
         if (mTrace.isA11yTracingEnabledForTypes(
-                FLAGS_WINDOW_MAGNIFICATION_CONNECTION
+                FLAGS_MAGNIFICATION_CONNECTION
                 | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
             mTrace.logTrace(TAG + ".setConnectionCallback",
-                    FLAGS_WINDOW_MAGNIFICATION_CONNECTION
+                    FLAGS_MAGNIFICATION_CONNECTION
                     | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
                     "callback=" + connectionCallback);
         }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 659112e..8ed3fd6 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -154,6 +154,7 @@
 
     static_libs: [
         "android.frameworks.location.altitude-V1-java", // AIDL
+        "android.frameworks.vibrator-V1-java", // AIDL
         "android.hardware.authsecret-V1.0-java",
         "android.hardware.authsecret-V1-java",
         "android.hardware.boot-V1.0-java", // HIDL
@@ -193,7 +194,6 @@
         "overlayable_policy_aidl-java",
         "SurfaceFlingerProperties",
         "com.android.sysprop.watchdog",
-        "ImmutabilityAnnotation",
         "securebox",
         "apache-commons-math",
         "backstage_power_flags_lib",
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b87d02d..6ec4fbc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -429,10 +429,10 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.HeptFunction;
+import com.android.internal.util.function.DodecFunction;
 import com.android.internal.util.function.HexFunction;
+import com.android.internal.util.function.OctFunction;
 import com.android.internal.util.function.QuadFunction;
-import com.android.internal.util.function.QuintFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.BootReceiver;
@@ -20149,20 +20149,21 @@
         }
 
         @Override
-        public int checkOperation(int code, int uid, String packageName,
-                String attributionTag, boolean raw,
-                QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) {
+        public int checkOperation(int code, int uid, String packageName, String attributionTag,
+                int virtualDeviceId, boolean raw, HexFunction<Integer, Integer, String, String,
+                        Integer, Boolean, Integer> superImpl) {
             if (uid == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
                         Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply(code, shellUid, "com.android.shell", null, raw);
+                    return superImpl.apply(code, shellUid, "com.android.shell", null,
+                            virtualDeviceId, raw);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(code, uid, packageName, attributionTag, raw);
+            return superImpl.apply(code, uid, packageName, attributionTag, virtualDeviceId, raw);
         }
 
         @Override
@@ -20183,23 +20184,24 @@
 
         @Override
         public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
-                @Nullable String featureId, boolean shouldCollectAsyncNotedOp,
+                @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
-                @NonNull HeptFunction<Integer, Integer, String, String, Boolean, String, Boolean,
-                        SyncNotedAppOp> superImpl) {
+                @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String,
+                        Boolean, SyncNotedAppOp> superImpl) {
             if (uid == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
                         Process.SHELL_UID);
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     return superImpl.apply(code, shellUid, "com.android.shell", featureId,
-                            shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                            virtualDeviceId, shouldCollectAsyncNotedOp, message,
+                            shouldCollectMessage);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(code, uid, packageName, featureId, shouldCollectAsyncNotedOp,
-                    message, shouldCollectMessage);
+            return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId,
+                    shouldCollectAsyncNotedOp, message, shouldCollectMessage);
         }
 
         @Override
@@ -20230,11 +20232,11 @@
 
         @Override
         public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
-                @Nullable String packageName, @Nullable String attributionTag,
+                @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId,
                 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
                 @AttributionFlags int attributionFlags, int attributionChainId,
-                @NonNull UndecFunction<IBinder, Integer, Integer, String, String, Boolean,
+                @NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean,
                         Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) {
             if (uid == mTargetUid && isTargetOp(code)) {
                 final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
@@ -20242,13 +20244,14 @@
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     return superImpl.apply(token, code, shellUid, "com.android.shell",
-                            attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                            shouldCollectMessage, attributionFlags, attributionChainId);
+                            attributionTag, virtualDeviceId, startIfModeDefault,
+                            shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                            attributionFlags, attributionChainId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(token, code, uid, packageName, attributionTag,
+            return superImpl.apply(token, code, uid, packageName, attributionTag, virtualDeviceId,
                     startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
                     attributionFlags, attributionChainId);
         }
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 95ef2b4..3e1edf2 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -180,6 +180,7 @@
         "tv_system_ui",
         "vibrator",
         "virtual_devices",
+        "wallet_integration",
         "wear_calling_messaging",
         "wear_connectivity",
         "wear_esim_carriers",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index a770b66..2ed079a 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -29,3 +29,10 @@
     description: "Disable BOOT_COMPLETED broadcast FGS start for certain types"
     bug: "296558535"
 }
+
+flag {
+    name: "bfgs_managed_network_access"
+    namespace: "backstage_power"
+    description: "Restrict network access for certain applications in BFGS process state"
+    bug: "304347838"
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7780b39..d80638a 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2580,17 +2580,30 @@
     public int checkOperationRaw(int code, int uid, String packageName,
             @Nullable String attributionTag) {
         return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
-                true /*raw*/);
+                Context.DEVICE_ID_DEFAULT, true /*raw*/);
+    }
+
+    @Override
+    public int checkOperationRawForDevice(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, int virtualDeviceId) {
+        return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
+                virtualDeviceId, true /*raw*/);
     }
 
     @Override
     public int checkOperation(int code, int uid, String packageName) {
         return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
-                false /*raw*/);
+                Context.DEVICE_ID_DEFAULT, false /*raw*/);
+    }
+
+    @Override
+    public int checkOperationForDevice(int code, int uid, String packageName, int virtualDeviceId) {
+        return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, null,
+                virtualDeviceId, false /*raw*/);
     }
 
     private int checkOperationImpl(int code, int uid, String packageName,
-            @Nullable String attributionTag, boolean raw) {
+             @Nullable String attributionTag, int virtualDeviceId, boolean raw) {
         verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
             return AppOpsManager.opToDefaultMode(code);
@@ -2816,12 +2829,23 @@
             String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage) {
         return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
-                attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message,
+                shouldCollectMessage);
+    }
+
+    @Override
+    public SyncNotedAppOp noteOperationForDevice(int code, int uid, @Nullable String packageName,
+            @Nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
+            String message, boolean shouldCollectMessage) {
+        return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName,
+                attributionTag, virtualDeviceId, shouldCollectAsyncNotedOp, message,
+                shouldCollectMessage);
     }
 
     private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
-            @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
-            @Nullable String message, boolean shouldCollectMessage) {
+             @Nullable String attributionTag, int virtualDeviceId,
+             boolean shouldCollectAsyncNotedOp, @Nullable String message,
+             boolean shouldCollectMessage) {
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
@@ -2840,10 +2864,10 @@
     }
 
     private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
-            @Nullable String attributionTag, int proxyUid, String proxyPackageName,
-            @Nullable String proxyAttributionTag, @OpFlags int flags,
-            boolean shouldCollectAsyncNotedOp, @Nullable String message,
-            boolean shouldCollectMessage) {
+           @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+           @Nullable String proxyAttributionTag, @OpFlags int flags,
+           boolean shouldCollectAsyncNotedOp, @Nullable String message,
+           boolean shouldCollectMessage) {
         PackageVerificationResult pvr;
         try {
             pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3238,12 +3262,26 @@
             String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
             int attributionChainId) {
         return mCheckOpsDelegateDispatcher.startOperation(token, code, uid, packageName,
-                attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                shouldCollectMessage, attributionFlags, attributionChainId);
+                attributionTag, Context.DEVICE_ID_DEFAULT, startIfModeDefault,
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
+                attributionChainId
+        );
+    }
+
+    @Override
+    public SyncNotedAppOp startOperationForDevice(IBinder token, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId,
+            boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
+            boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+            int attributionChainId) {
+        return mCheckOpsDelegateDispatcher.startOperation(token, code, uid, packageName,
+                attributionTag, virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp,
+                message, shouldCollectMessage, attributionFlags, attributionChainId
+        );
     }
 
     private SyncNotedAppOp startOperationImpl(@NonNull IBinder clientId, int code, int uid,
-            @Nullable String packageName, @Nullable String attributionTag,
+            @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
             int attributionChainId) {
@@ -3614,11 +3652,18 @@
     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
             String attributionTag) {
         mCheckOpsDelegateDispatcher.finishOperation(clientId, code, uid, packageName,
-                attributionTag);
+                attributionTag, Context.DEVICE_ID_DEFAULT);
+    }
+
+    @Override
+    public void finishOperationForDevice(IBinder clientId, int code, int uid,
+            @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId) {
+        mCheckOpsDelegateDispatcher.finishOperation(clientId, code, uid, packageName,
+                attributionTag, virtualDeviceId);
     }
 
     private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
-            String attributionTag) {
+            String attributionTag, int virtualDeviceId) {
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
@@ -6800,25 +6845,28 @@
         }
 
         public int checkOperation(int code, int uid, String packageName,
-                @Nullable String attributionTag, boolean raw) {
+                @Nullable String attributionTag, int virtualDeviceId, boolean raw) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    return mPolicy.checkOperation(code, uid, packageName, attributionTag, raw,
-                            this::checkDelegateOperationImpl);
+                    return mPolicy.checkOperation(code, uid, packageName, attributionTag,
+                            virtualDeviceId, raw, this::checkDelegateOperationImpl
+                    );
                 } else {
-                    return mPolicy.checkOperation(code, uid, packageName, attributionTag, raw,
-                            AppOpsService.this::checkOperationImpl);
+                    return mPolicy.checkOperation(code, uid, packageName, attributionTag,
+                            virtualDeviceId, raw, AppOpsService.this::checkOperationImpl
+                    );
                 }
             } else if (mCheckOpsDelegate != null) {
-                return checkDelegateOperationImpl(code, uid, packageName, attributionTag, raw);
+                return checkDelegateOperationImpl(code, uid, packageName, attributionTag,
+                        virtualDeviceId, raw);
             }
-            return checkOperationImpl(code, uid, packageName, attributionTag, raw);
+            return checkOperationImpl(code, uid, packageName, attributionTag, virtualDeviceId, raw);
         }
 
         private int checkDelegateOperationImpl(int code, int uid, String packageName,
-                @Nullable String attributionTag, boolean raw) {
-            return mCheckOpsDelegate.checkOperation(code, uid, packageName, attributionTag, raw,
-                    AppOpsService.this::checkOperationImpl);
+                 @Nullable String attributionTag, int virtualDeviceId, boolean raw) {
+            return mCheckOpsDelegate.checkOperation(code, uid, packageName, attributionTag,
+                    virtualDeviceId, raw, AppOpsService.this::checkOperationImpl);
         }
 
         public int checkAudioOperation(int code, int usage, int uid, String packageName) {
@@ -6843,33 +6891,36 @@
         }
 
         public SyncNotedAppOp noteOperation(int code, int uid, String packageName,
-                String attributionTag, boolean shouldCollectAsyncNotedOp, String message,
-                boolean shouldCollectMessage) {
+                String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp,
+                String message, boolean shouldCollectMessage) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
                     return mPolicy.noteOperation(code, uid, packageName, attributionTag,
-                            shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                            this::noteDelegateOperationImpl);
+                            virtualDeviceId, shouldCollectAsyncNotedOp, message,
+                            shouldCollectMessage, this::noteDelegateOperationImpl
+                    );
                 } else {
                     return mPolicy.noteOperation(code, uid, packageName, attributionTag,
-                            shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                            AppOpsService.this::noteOperationImpl);
+                            virtualDeviceId, shouldCollectAsyncNotedOp, message,
+                            shouldCollectMessage, AppOpsService.this::noteOperationImpl
+                    );
                 }
             } else if (mCheckOpsDelegate != null) {
-                return noteDelegateOperationImpl(code, uid, packageName,
-                        attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                return noteDelegateOperationImpl(code, uid, packageName, attributionTag,
+                        virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
             }
             return noteOperationImpl(code, uid, packageName, attributionTag,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+                    virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
         }
 
         private SyncNotedAppOp noteDelegateOperationImpl(int code, int uid,
-                @Nullable String packageName, @Nullable String featureId,
+                @Nullable String packageName, @Nullable String featureId, int virtualDeviceId,
                 boolean shouldCollectAsyncNotedOp, @Nullable String message,
                 boolean shouldCollectMessage) {
             return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId,
-                    shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                    AppOpsService.this::noteOperationImpl);
+                    virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                    AppOpsService.this::noteOperationImpl
+            );
         }
 
         public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
@@ -6904,40 +6955,45 @@
         }
 
         public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
-                @Nullable String packageName, @NonNull String attributionTag,
+                @Nullable String packageName, @NonNull String attributionTag, int virtualDeviceId,
                 boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
                 @Nullable String message, boolean shouldCollectMessage,
                 @AttributionFlags int attributionFlags, int attributionChainId) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
-                    return mPolicy.startOperation(token, code, uid, packageName,
-                            attributionTag, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                    return mPolicy.startOperation(token, code, uid, packageName, attributionTag,
+                            virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, attributionFlags, attributionChainId,
-                            this::startDelegateOperationImpl);
+                            this::startDelegateOperationImpl
+                    );
                 } else {
                     return mPolicy.startOperation(token, code, uid, packageName, attributionTag,
-                            startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                            virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message,
                             shouldCollectMessage, attributionFlags, attributionChainId,
-                            AppOpsService.this::startOperationImpl);
+                            AppOpsService.this::startOperationImpl
+                    );
                 }
             } else if (mCheckOpsDelegate != null) {
                 return startDelegateOperationImpl(token, code, uid, packageName, attributionTag,
-                        startIfModeDefault, shouldCollectAsyncNotedOp, message,
-                        shouldCollectMessage, attributionFlags, attributionChainId);
+                        virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                        shouldCollectMessage, attributionFlags, attributionChainId
+                );
             }
             return startOperationImpl(token, code, uid, packageName, attributionTag,
-                    startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                    attributionFlags, attributionChainId);
+                    virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                    shouldCollectMessage, attributionFlags, attributionChainId
+            );
         }
 
         private SyncNotedAppOp startDelegateOperationImpl(IBinder token, int code, int uid,
                 @Nullable String packageName, @Nullable String attributionTag,
-                boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
-                boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
-                int attributionChainId) {
+                int virtualDeviceId, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
+                String message, boolean shouldCollectMessage,
+                @AttributionFlags int attributionFlags, int attributionChainId) {
             return mCheckOpsDelegate.startOperation(token, code, uid, packageName, attributionTag,
-                    startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
-                    attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
+                    virtualDeviceId, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+                    shouldCollectMessage, attributionFlags, attributionChainId,
+                    AppOpsService.this::startOperationImpl);
         }
 
         public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@@ -6982,26 +7038,28 @@
         }
 
         public void finishOperation(IBinder clientId, int code, int uid, String packageName,
-                String attributionTag) {
+                String attributionTag, int virtualDeviceId) {
             if (mPolicy != null) {
                 if (mCheckOpsDelegate != null) {
                     mPolicy.finishOperation(clientId, code, uid, packageName, attributionTag,
-                            this::finishDelegateOperationImpl);
+                            virtualDeviceId, this::finishDelegateOperationImpl);
                 } else {
                     mPolicy.finishOperation(clientId, code, uid, packageName, attributionTag,
-                            AppOpsService.this::finishOperationImpl);
+                            virtualDeviceId, AppOpsService.this::finishOperationImpl);
                 }
             } else if (mCheckOpsDelegate != null) {
-                finishDelegateOperationImpl(clientId, code, uid, packageName, attributionTag);
+                finishDelegateOperationImpl(clientId, code, uid, packageName, attributionTag,
+                        virtualDeviceId);
             } else {
-                finishOperationImpl(clientId, code, uid, packageName, attributionTag);
+                finishOperationImpl(clientId, code, uid, packageName, attributionTag,
+                        virtualDeviceId);
             }
         }
 
         private void finishDelegateOperationImpl(IBinder clientId, int code, int uid,
-                String packageName, String attributionTag) {
+                String packageName, String attributionTag, int virtualDeviceId) {
             mCheckOpsDelegate.finishOperation(clientId, code, uid, packageName, attributionTag,
-                    AppOpsService.this::finishOperationImpl);
+                    virtualDeviceId, AppOpsService.this::finishOperationImpl);
         }
 
         public void finishProxyOperation(@NonNull IBinder clientId, int code,
diff --git a/services/core/java/com/android/server/appop/AudioRestrictionManager.java b/services/core/java/com/android/server/appop/AudioRestrictionManager.java
index be87037..b9ccc53 100644
--- a/services/core/java/com/android/server/appop/AudioRestrictionManager.java
+++ b/services/core/java/com/android/server/appop/AudioRestrictionManager.java
@@ -43,7 +43,7 @@
     static {
         SparseBooleanArray audioMutedUsages = new SparseBooleanArray();
         SparseBooleanArray vibrationMutedUsages = new SparseBooleanArray();
-        for (int usage : AudioAttributes.SDK_USAGES) {
+        for (int usage : AudioAttributes.SDK_USAGES.toArray()) {
             final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
             if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NOTIFICATION ||
                     suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL ||
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index 5c8dd0d..b91e633 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -19,6 +19,7 @@
 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
 import static android.media.AudioSystem.DEVICE_NONE;
 import static android.media.AudioSystem.isBluetoothDevice;
+import static android.media.audio.Flags.automaticBtDeviceType;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -55,6 +56,8 @@
     @AudioManager.AudioDeviceCategory
     private int mAudioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN;
 
+    private boolean mAutoBtCategorySet = false;
+
     private boolean mSAEnabled;
     private boolean mHasHeadTracker = false;
     private boolean mHeadTrackerEnabled;
@@ -84,58 +87,94 @@
         mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress);
     }
 
-    public Pair<Integer, String> getDeviceId() {
+    public synchronized Pair<Integer, String> getDeviceId() {
         return mDeviceId;
     }
 
     @AudioDeviceInfo.AudioDeviceType
-    public int getDeviceType() {
+    public synchronized int getDeviceType() {
         return mDeviceType;
     }
 
-    public int getInternalDeviceType() {
+    public synchronized int getInternalDeviceType() {
         return mInternalDeviceType;
     }
 
     @NonNull
-    public String getDeviceAddress() {
+    public synchronized String getDeviceAddress() {
         return mDeviceAddress;
     }
 
-    public void setSAEnabled(boolean sAEnabled) {
+    public synchronized void setSAEnabled(boolean sAEnabled) {
         mSAEnabled = sAEnabled;
     }
 
-    public boolean isSAEnabled() {
+    public synchronized boolean isSAEnabled() {
         return mSAEnabled;
     }
 
-    public void setHeadTrackerEnabled(boolean headTrackerEnabled) {
+    public synchronized void setHeadTrackerEnabled(boolean headTrackerEnabled) {
         mHeadTrackerEnabled = headTrackerEnabled;
     }
 
-    public boolean isHeadTrackerEnabled() {
+    public synchronized boolean isHeadTrackerEnabled() {
         return mHeadTrackerEnabled;
     }
 
-    public void setHasHeadTracker(boolean hasHeadTracker) {
+    public synchronized void setHasHeadTracker(boolean hasHeadTracker) {
         mHasHeadTracker = hasHeadTracker;
     }
 
 
-    public boolean hasHeadTracker() {
+    public synchronized boolean hasHeadTracker() {
         return mHasHeadTracker;
     }
 
     @AudioDeviceInfo.AudioDeviceType
-    public int getAudioDeviceCategory() {
+    public synchronized int getAudioDeviceCategory() {
         return mAudioDeviceCategory;
     }
 
-    public void setAudioDeviceCategory(@AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) {
+    public synchronized void setAudioDeviceCategory(
+            @AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) {
         mAudioDeviceCategory = audioDeviceCategory;
     }
 
+    public synchronized boolean isBtDeviceCategoryFixed() {
+        if (!automaticBtDeviceType()) {
+            // do nothing
+            return false;
+        }
+
+        updateAudioDeviceCategory();
+        return mAutoBtCategorySet;
+    }
+
+    public synchronized boolean updateAudioDeviceCategory() {
+        if (!automaticBtDeviceType()) {
+            // do nothing
+            return false;
+        }
+        if (!isBluetoothDevice(mInternalDeviceType)) {
+            return false;
+        }
+        if (mAutoBtCategorySet) {
+            // no need to update. The auto value is already set.
+            return false;
+        }
+
+        int newAudioDeviceCategory = BtHelper.getBtDeviceCategory(mDeviceAddress);
+        if (newAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_UNKNOWN) {
+            // no info provided by the BtDevice metadata
+            return false;
+        }
+
+        mAudioDeviceCategory = newAudioDeviceCategory;
+        mAutoBtCategorySet = true;
+        return true;
+
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (this == obj) {
@@ -175,7 +214,7 @@
                 + " HTenabled: " + mHeadTrackerEnabled;
     }
 
-    public String toPersistableString() {
+    public synchronized String toPersistableString() {
         return (new StringBuilder().append(mDeviceType)
                 .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress)
                 .append(SETTING_FIELD_SEPARATOR).append(mSAEnabled ? "1" : "0")
@@ -228,6 +267,8 @@
             deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1);
             deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1);
             deviceState.setAudioDeviceCategory(audioDeviceCategory);
+            // update in case we can automatically determine the category
+            deviceState.updateAudioDeviceCategory();
             return deviceState;
         } catch (NumberFormatException e) {
             Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e);
@@ -235,7 +276,7 @@
         }
     }
 
-    public AudioDeviceAttributes getAudioDeviceAttributes() {
+    public synchronized AudioDeviceAttributes getAudioDeviceAttributes() {
         return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
                 mDeviceType, mDeviceAddress);
     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b1706ed..865c2ab 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -30,6 +30,7 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.media.AudioManager.AudioDeviceCategory;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
@@ -1483,8 +1484,12 @@
                 MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES, SENDMSG_QUEUE, groupId);
     }
 
-    /*package*/ void postSynchronizeLeDevicesInInventory(AdiDeviceState deviceState) {
-        sendLMsgNoDelay(MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState);
+    /*package*/ void postSynchronizeAdiDevicesInInventory(AdiDeviceState deviceState) {
+        sendLMsgNoDelay(MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState);
+    }
+
+    /*package*/ void postUpdatedAdiDeviceState(AdiDeviceState deviceState) {
+        sendLMsgNoDelay(MSG_L_UPDATED_ADI_DEVICE_STATE, SENDMSG_QUEUE, deviceState);
     }
 
     /*package*/ static final class CommunicationDeviceInfo {
@@ -1595,8 +1600,8 @@
         sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
     }
 
-    /*package*/ void setLeAudioTimeout(String address, int device, int delayMs) {
-        sendILMsg(MSG_IL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, address, delayMs);
+    /*package*/ void setLeAudioTimeout(String address, int device, int codec, int delayMs) {
+        sendIILMsg(MSG_IIL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, codec, address, delayMs);
     }
 
     /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
@@ -1794,8 +1799,9 @@
                                 return;
                             }
                             @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
-                                    mBtHelper.getA2dpCodecWithFallbackToSBC(
-                                            btInfo.mDevice, "MSG_L_SET_BT_ACTIVE_DEVICE");
+                                    mBtHelper.getCodecWithFallback(btInfo.mDevice,
+                                            btInfo.mProfile, btInfo.mIsLeOutput,
+                                            "MSG_L_SET_BT_ACTIVE_DEVICE");
                             mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
                                     (btInfo.mProfile
                                             != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
@@ -1819,22 +1825,24 @@
                 case MSG_IL_BTA2DP_TIMEOUT:
                     // msg.obj  == address of BTA2DP device
                     synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+                        mDeviceInventory.onMakeA2dpDeviceUnavailableNow(
+                                (String) msg.obj, msg.arg1);
                     }
                     break;
-                case MSG_IL_BTLEAUDIO_TIMEOUT:
+                case MSG_IIL_BTLEAUDIO_TIMEOUT:
                     // msg.obj  == address of LE Audio device
                     synchronized (mDeviceStateLock) {
                         mDeviceInventory.onMakeLeAudioDeviceUnavailableNow(
-                                (String) msg.obj, msg.arg1);
+                                (String) msg.obj, msg.arg1, msg.arg2);
                     }
                     break;
                 case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
                     final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
                     synchronized (mDeviceStateLock) {
                         @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
-                                mBtHelper.getA2dpCodecWithFallbackToSBC(
-                                        btInfo.mDevice, "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
+                                mBtHelper.getCodecWithFallback(btInfo.mDevice,
+                                        btInfo.mProfile, btInfo.mIsLeOutput,
+                                        "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
                         mDeviceInventory.onBluetoothDeviceConfigChange(
                                 btInfo, codec, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
                     }
@@ -2004,14 +2012,19 @@
                         }
                     } break;
 
-                case MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY:
+                case MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
-                            mDeviceInventory.onSynchronizeLeDevicesInInventory(
+                            mDeviceInventory.onSynchronizeAdiDevicesInInventory(
                                     (AdiDeviceState) msg.obj);
                         }
                     } break;
 
+                case MSG_L_UPDATED_ADI_DEVICE_STATE:
+                    synchronized (mDeviceStateLock) {
+                        mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj);
+                    } break;
+
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -2084,7 +2097,7 @@
 
     private static final int MSG_IL_SAVE_NDEF_DEVICE_FOR_STRATEGY = 47;
     private static final int MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY = 48;
-    private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49;
+    private static final int MSG_IIL_BTLEAUDIO_TIMEOUT = 49;
 
     private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
     private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53;
@@ -2095,7 +2108,8 @@
 
     private static final int MSG_CHECK_COMMUNICATION_ROUTE_CLIENT_STATE = 56;
     private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57;
-    private static final int MSG_L_SYNCHRONIZE_LE_DEVICES_IN_INVENTORY = 58;
+    private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58;
+    private static final int MSG_L_UPDATED_ADI_DEVICE_STATE = 59;
 
 
 
@@ -2104,7 +2118,7 @@
             case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
             case MSG_L_SET_BT_ACTIVE_DEVICE:
             case MSG_IL_BTA2DP_TIMEOUT:
-            case MSG_IL_BTLEAUDIO_TIMEOUT:
+            case MSG_IIL_BTLEAUDIO_TIMEOUT:
             case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
             case MSG_TOGGLE_HDMI:
             case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
@@ -2196,7 +2210,7 @@
                 case MSG_L_SET_BT_ACTIVE_DEVICE:
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
                 case MSG_IL_BTA2DP_TIMEOUT:
-                case MSG_IL_BTLEAUDIO_TIMEOUT:
+                case MSG_IIL_BTLEAUDIO_TIMEOUT:
                 case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
                     if (sLastDeviceConnectMsgTime >= time) {
                         // add a little delay to make sure messages are ordered as expected
@@ -2742,6 +2756,21 @@
         return mDeviceInventory.findBtDeviceStateForAddress(address, deviceType);
     }
 
+    void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address,
+            @AudioDeviceCategory int btAudioDeviceCategory) {
+        mDeviceInventory.addAudioDeviceWithCategoryInInventoryIfNeeded(address,
+                btAudioDeviceCategory);
+    }
+
+    @AudioDeviceCategory
+    int getAndUpdateBtAdiDeviceStateCategoryForAddress(@NonNull String address) {
+        return mDeviceInventory.getAndUpdateBtAdiDeviceStateCategoryForAddress(address);
+    }
+
+    boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) {
+        return mDeviceInventory.isBluetoothAudioDeviceCategoryFixed(address);
+    }
+
     //------------------------------------------------
     // for testing purposes only
     void clearDeviceInventory() {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e9b102b..5499fd5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -15,16 +15,20 @@
  */
 package com.android.server.audio;
 
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
 import static android.media.AudioSystem.DEVICE_IN_ALL_SCO_SET;
 import static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET;
 import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET;
 import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO_SET;
+import static android.media.AudioSystem.DEVICE_OUT_BLE_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
 import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID;
 import static android.media.AudioSystem.isBluetoothA2dpOutDevice;
 import static android.media.AudioSystem.isBluetoothDevice;
 import static android.media.AudioSystem.isBluetoothLeOutDevice;
 import static android.media.AudioSystem.isBluetoothOutDevice;
 import static android.media.AudioSystem.isBluetoothScoOutDevice;
+import static android.media.audio.Flags.automaticBtDeviceType;
 
 
 import android.annotation.NonNull;
@@ -39,6 +43,7 @@
 import android.media.AudioDevicePort;
 import android.media.AudioFormat;
 import android.media.AudioManager;
+import android.media.AudioManager.AudioDeviceCategory;
 import android.media.AudioPort;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
@@ -82,6 +87,7 @@
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Stream;
 
 /**
@@ -108,9 +114,11 @@
     private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>();
 
     Collection<AdiDeviceState> getImmutableDeviceInventory() {
+        final List<AdiDeviceState> newList;
         synchronized (mDeviceInventoryLock) {
-            return mDeviceInventory.values();
+            newList = new ArrayList<>(mDeviceInventory.values());
         }
+        return newList;
     }
 
     /**
@@ -127,30 +135,43 @@
                 return oldState;
             });
         }
-        mDeviceBroker.postSynchronizeLeDevicesInInventory(deviceState);
+        mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
     }
 
     /**
-     * Adds a new entry in mDeviceInventory if the AudioDeviceAttributes passed is an sink
+     * Adds a new entry in mDeviceInventory if the attributes passed represent a sink
      * Bluetooth device and no corresponding entry already exists.
-     * @param ada the device to add if needed
+     *
+     * <p>This method will reconcile all BT devices connected with different profiles
+     * that share the same MAC address and will also synchronize the devices to their
+     * corresponding peers in case of BLE
      */
-    void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddres) {
+    void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress,
+            @AudioDeviceCategory int category) {
         if (!isBluetoothOutDevice(deviceType)) {
             return;
         }
         synchronized (mDeviceInventoryLock) {
             AdiDeviceState ads = findBtDeviceStateForAddress(address, deviceType);
-            if (ads == null) {
-                ads = findBtDeviceStateForAddress(peerAddres, deviceType);
+            if (ads == null && peerAddress != null) {
+                ads = findBtDeviceStateForAddress(peerAddress, deviceType);
             }
             if (ads != null) {
-                mDeviceBroker.postSynchronizeLeDevicesInInventory(ads);
+                if (ads.getAudioDeviceCategory() != category
+                        && category != AUDIO_DEVICE_CATEGORY_UNKNOWN) {
+                    ads.setAudioDeviceCategory(category);
+                    mDeviceBroker.postUpdatedAdiDeviceState(ads);
+                    mDeviceBroker.postPersistAudioDeviceSettings();
+                }
+                mDeviceBroker.postSynchronizeAdiDevicesInInventory(ads);
                 return;
             }
             ads = new AdiDeviceState(AudioDeviceInfo.convertInternalDeviceToDeviceType(deviceType),
                     deviceType, address);
+            ads.setAudioDeviceCategory(category);
+
             mDeviceInventory.put(ads.getDeviceId(), ads);
+            mDeviceBroker.postUpdatedAdiDeviceState(ads);
             mDeviceBroker.postPersistAudioDeviceSettings();
         }
     }
@@ -161,63 +182,86 @@
      * @param deviceState the device to update
      */
     void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) {
+        AtomicBoolean updatedCategory = new AtomicBoolean(false);
         synchronized (mDeviceInventoryLock) {
-            mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> {
-                oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory());
-                return oldState;
-            });
+            if (automaticBtDeviceType()) {
+                if (deviceState.updateAudioDeviceCategory()) {
+                    updatedCategory.set(true);
+                }
+            }
+            deviceState = mDeviceInventory.merge(deviceState.getDeviceId(),
+                    deviceState, (oldState, newState) -> {
+                        if (oldState.getAudioDeviceCategory()
+                                != newState.getAudioDeviceCategory()) {
+                            oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory());
+                            updatedCategory.set(true);
+                        }
+                        return oldState;
+                    });
         }
-        mDeviceBroker.postSynchronizeLeDevicesInInventory(deviceState);
+        if (updatedCategory.get()) {
+            mDeviceBroker.postUpdatedAdiDeviceState(deviceState);
+        }
+        mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
+    }
+
+    void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address,
+            @AudioDeviceCategory int btAudioDeviceCategory) {
+        addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLE_HEADSET,
+                address, "", btAudioDeviceCategory);
+        addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLUETOOTH_A2DP,
+                address, "", btAudioDeviceCategory);
+
+    }
+    @AudioDeviceCategory
+    int getAndUpdateBtAdiDeviceStateCategoryForAddress(@NonNull String address) {
+        int btCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        boolean bleCategoryFound = false;
+        AdiDeviceState deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLE_HEADSET);
+        if (deviceState != null) {
+            addOrUpdateAudioDeviceCategoryInInventory(deviceState);
+            btCategory = deviceState.getAudioDeviceCategory();
+            bleCategoryFound = true;
+        }
+
+        deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLUETOOTH_A2DP);
+        if (deviceState != null) {
+            addOrUpdateAudioDeviceCategoryInInventory(deviceState);
+            int a2dpCategory = deviceState.getAudioDeviceCategory();
+            if (bleCategoryFound && a2dpCategory != btCategory) {
+                Log.w(TAG, "Found different audio device category for A2DP and BLE profiles with "
+                        + "address " + address);
+            }
+            btCategory = a2dpCategory;
+        }
+
+        return btCategory;
+    }
+
+    boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) {
+        AdiDeviceState deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLE_HEADSET);
+        if (deviceState != null) {
+            return deviceState.isBtDeviceCategoryFixed();
+        }
+
+        deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLUETOOTH_A2DP);
+        if (deviceState != null) {
+            return deviceState.isBtDeviceCategoryFixed();
+        }
+
+        return false;
     }
 
     /**
      * synchronize AdiDeviceState for LE devices in the same group
      */
-    void onSynchronizeLeDevicesInInventory(AdiDeviceState updatedDevice) {
+    void onSynchronizeAdiDevicesInInventory(AdiDeviceState updatedDevice) {
         synchronized (mDevicesLock) {
             synchronized (mDeviceInventoryLock) {
                 boolean found = false;
-                for (DeviceInfo di : mConnectedDevices.values()) {
-                    if (di.mDeviceType != updatedDevice.getInternalDeviceType()) {
-                        continue;
-                    }
-                    if (di.mDeviceAddress.equals(updatedDevice.getDeviceAddress())) {
-                        for (AdiDeviceState ads2 : mDeviceInventory.values()) {
-                            if (!(di.mDeviceType == ads2.getInternalDeviceType()
-                                    && di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
-                                continue;
-                            }
-                            ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
-                            ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
-                            ads2.setSAEnabled(updatedDevice.isSAEnabled());
-                            ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
-                            found = true;
-                            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                                    "onSynchronizeLeDevicesInInventory synced device pair ads1="
-                                    + updatedDevice + " ads2=" + ads2).printLog(TAG));
-                            break;
-                        }
-                    }
-                    if (di.mPeerDeviceAddress.equals(updatedDevice.getDeviceAddress())) {
-                        for (AdiDeviceState ads2 : mDeviceInventory.values()) {
-                            if (!(di.mDeviceType == ads2.getInternalDeviceType()
-                                    && di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
-                                continue;
-                            }
-                            ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
-                            ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
-                            ads2.setSAEnabled(updatedDevice.isSAEnabled());
-                            ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
-                            found = true;
-                            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                                    "onSynchronizeLeDevicesInInventory synced device pair ads1="
-                                    + updatedDevice + " peer ads2=" + ads2).printLog(TAG));
-                            break;
-                        }
-                    }
-                    if (found) {
-                        break;
-                    }
+                found |= synchronizeBleDeviceInInventory(updatedDevice);
+                if (automaticBtDeviceType()) {
+                    found |= synchronizeDeviceProfilesInInventory(updatedDevice);
                 }
                 if (found) {
                     mDeviceBroker.postPersistAudioDeviceSettings();
@@ -226,12 +270,80 @@
         }
     }
 
+    @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"})
+    private boolean synchronizeBleDeviceInInventory(AdiDeviceState updatedDevice) {
+        for (DeviceInfo di : mConnectedDevices.values()) {
+            if (di.mDeviceType != updatedDevice.getInternalDeviceType()) {
+                continue;
+            }
+            if (di.mDeviceAddress.equals(updatedDevice.getDeviceAddress())) {
+                for (AdiDeviceState ads2 : mDeviceInventory.values()) {
+                    if (!(di.mDeviceType == ads2.getInternalDeviceType()
+                            && di.mPeerDeviceAddress.equals(ads2.getDeviceAddress()))) {
+                        continue;
+                    }
+                    ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+                    ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+                    ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
+
+                    mDeviceBroker.postUpdatedAdiDeviceState(ads2);
+                    AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                            "synchronizeBleDeviceInInventory synced device pair ads1="
+                                    + updatedDevice + " ads2=" + ads2).printLog(TAG));
+                    return true;
+                }
+            }
+            if (di.mPeerDeviceAddress.equals(updatedDevice.getDeviceAddress())) {
+                for (AdiDeviceState ads2 : mDeviceInventory.values()) {
+                    if (!(di.mDeviceType == ads2.getInternalDeviceType()
+                            && di.mDeviceAddress.equals(ads2.getDeviceAddress()))) {
+                        continue;
+                    }
+                    ads2.setHasHeadTracker(updatedDevice.hasHeadTracker());
+                    ads2.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+                    ads2.setSAEnabled(updatedDevice.isSAEnabled());
+                    ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
+
+                    mDeviceBroker.postUpdatedAdiDeviceState(ads2);
+                    AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                            "synchronizeBleDeviceInInventory synced device pair ads1="
+                                    + updatedDevice + " peer ads2=" + ads2).printLog(TAG));
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @GuardedBy("mDeviceInventoryLock")
+    private boolean synchronizeDeviceProfilesInInventory(AdiDeviceState updatedDevice) {
+        for (AdiDeviceState ads : mDeviceInventory.values()) {
+            if (updatedDevice.getInternalDeviceType() == ads.getInternalDeviceType()
+                    || !updatedDevice.getDeviceAddress().equals(ads.getDeviceAddress())) {
+                continue;
+            }
+
+            ads.setHasHeadTracker(updatedDevice.hasHeadTracker());
+            ads.setHeadTrackerEnabled(updatedDevice.isHeadTrackerEnabled());
+            ads.setSAEnabled(updatedDevice.isSAEnabled());
+            ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
+
+            mDeviceBroker.postUpdatedAdiDeviceState(ads);
+            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                    "synchronizeDeviceProfilesInInventory synced device pair ads1="
+                            + updatedDevice + " ads2=" + ads).printLog(TAG));
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Finds the BT device that matches the passed {@code address}. Currently, this method only
      * returns a valid device for A2DP and BLE devices.
      *
      * @param address MAC address of BT device
-     * @param isBle true if the device is BLE, false for A2DP
+     * @param deviceType internal device type to identify the BT device
      * @return the found {@link AdiDeviceState} or {@code null} otherwise.
      */
     @Nullable
@@ -689,9 +801,11 @@
                 case BluetoothProfile.LE_AUDIO:
                 case BluetoothProfile.LE_AUDIO_BROADCAST:
                     if (switchToUnavailable) {
-                        makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice);
+                        makeLeAudioDeviceUnavailableNow(address,
+                                btInfo.mAudioSystemDevice, di.mDeviceCodecFormat);
                     } else if (switchToAvailable) {
-                        makeLeAudioDeviceAvailable(btInfo, streamType, "onSetBtActiveDevice");
+                        makeLeAudioDeviceAvailable(
+                                btInfo, streamType, codec, "onSetBtActiveDevice");
                     }
                     break;
                 default: throw new IllegalArgumentException("Invalid profile "
@@ -752,12 +866,13 @@
 
 
             if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
-                boolean a2dpCodecChange = false;
-                if (btInfo.mProfile == BluetoothProfile.A2DP) {
+                boolean codecChange = false;
+                if (btInfo.mProfile == BluetoothProfile.A2DP
+                        || btInfo.mProfile == BluetoothProfile.LE_AUDIO) {
                     if (di.mDeviceCodecFormat != codec) {
                         di.mDeviceCodecFormat = codec;
                         mConnectedDevices.replace(key, di);
-                        a2dpCodecChange = true;
+                        codecChange = true;
                     }
                     final int res = mAudioSystem.handleDeviceConfigChange(
                             btInfo.mAudioSystemDevice, address, BtHelper.getName(btDevice), codec);
@@ -782,7 +897,7 @@
 
                     }
                 }
-                if (!a2dpCodecChange) {
+                if (!codecChange) {
                     updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/);
                 }
             }
@@ -796,9 +911,9 @@
         }
     }
 
-    /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device) {
+    /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device, int codec) {
         synchronized (mDevicesLock) {
-            makeLeAudioDeviceUnavailableNow(address, device);
+            makeLeAudioDeviceUnavailableNow(address, device, codec);
         }
     }
 
@@ -1335,6 +1450,27 @@
         }
     }
 
+    private static boolean devicesListEqual(@NonNull List<AudioDeviceAttributes> list1,
+                                            @NonNull List<AudioDeviceAttributes> list2) {
+        if (list1.size() != list2.size()) {
+            return false;
+        }
+        // This assumes a given device is only present once in a list
+        for (AudioDeviceAttributes d1 : list1) {
+            boolean found = false;
+            for (AudioDeviceAttributes d2 : list2) {
+                if (d1.equalTypeAddress(d2)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private int setDevicesRole(
             ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
             AudioSystemInterface addOp,
@@ -1342,31 +1478,26 @@
             int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) {
         synchronized (rolesMap) {
             Pair<Integer, Integer> key = new Pair<>(useCase, role);
-            List<AudioDeviceAttributes> roleDevices = new ArrayList<>();
-            List<AudioDeviceAttributes> appliedDevices = new ArrayList<>();
-
             if (rolesMap.containsKey(key)) {
-                roleDevices = rolesMap.get(key);
-                boolean equal = false;
-                if (roleDevices.size() == devices.size()) {
-                    roleDevices.retainAll(devices);
-                    equal = roleDevices.size() == devices.size();
+                if (devicesListEqual(devices, rolesMap.get(key))) {
+                    // NO OP: no change in preference
+                    return AudioSystem.SUCCESS;
                 }
-                if (!equal) {
-                    clearOp.deviceRoleAction(useCase, role, null);
-                    roleDevices.clear();
-                    appliedDevices.addAll(devices);
-                }
-            } else {
-                appliedDevices.addAll(devices);
-            }
-            if (appliedDevices.isEmpty()) {
+            } else if (devices.isEmpty()) {
+                // NO OP: no preference to no preference
                 return AudioSystem.SUCCESS;
             }
-            final int status = addOp.deviceRoleAction(useCase, role, appliedDevices);
-            if (status == AudioSystem.SUCCESS) {
-                roleDevices.addAll(appliedDevices);
-                rolesMap.put(key, roleDevices);
+            int status;
+            if (devices.isEmpty()) {
+                status = clearOp.deviceRoleAction(useCase, role, null);
+                if (status == AudioSystem.SUCCESS) {
+                    rolesMap.remove(key);
+                }
+            } else {
+                status = addOp.deviceRoleAction(useCase, role, devices);
+                if (status == AudioSystem.SUCCESS) {
+                    rolesMap.put(key, devices);
+                }
             }
             return status;
         }
@@ -1528,7 +1659,8 @@
                     if (!connect) {
                         purgeDevicesRoles_l();
                     } else {
-                        addAudioDeviceInInventoryIfNeeded(device, address, "");
+                        addAudioDeviceInInventoryIfNeeded(device, address, "",
+                                BtHelper.getBtDeviceCategory(address));
                     }
                 }
                 mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
@@ -1641,11 +1773,12 @@
         }
 
         synchronized (mDevicesLock) {
-            final ArraySet<String> toRemove = new ArraySet<>();
+            final ArraySet<Pair<String, Integer>> toRemove = new ArraySet<>();
             // Disconnect ALL DEVICE_OUT_BLE_HEADSET or DEVICE_OUT_BLE_BROADCAST devices
             mConnectedDevices.values().forEach(deviceInfo -> {
                 if (deviceInfo.mDeviceType == device) {
-                    toRemove.add(deviceInfo.mDeviceAddress);
+                    toRemove.add(
+                            new Pair<>(deviceInfo.mDeviceAddress, deviceInfo.mDeviceCodecFormat));
                 }
             });
             new MediaMetrics.Item(mMetricsId + "disconnectLeAudio")
@@ -1655,8 +1788,8 @@
                 final int delay = checkSendBecomingNoisyIntentInt(device,
                         AudioService.CONNECTION_STATE_DISCONNECTED,
                         AudioSystem.DEVICE_NONE);
-                toRemove.stream().forEach(deviceAddress ->
-                        makeLeAudioDeviceUnavailableLater(deviceAddress, device, delay)
+                toRemove.stream().forEach(entry ->
+                        makeLeAudioDeviceUnavailableLater(entry.first, device, entry.second, delay)
                 );
             }
         }
@@ -1809,7 +1942,9 @@
         setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
 
         updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
-        addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "");
+
+        addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "",
+                BtHelper.getBtDeviceCategory(address));
     }
 
     static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2129,7 +2264,8 @@
         mDeviceBroker.postApplyVolumeOnDevice(streamType,
                 DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
         setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
-        addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "");
+        addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "",
+                BtHelper.getBtDeviceCategory(address));
         new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
                 .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
                 .set(MediaMetrics.Property.DEVICE,
@@ -2200,7 +2336,8 @@
 
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceAvailable(
-            AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
+            AudioDeviceBroker.BtDeviceInfo btInfo, int streamType,
+            @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, String eventSource) {
         final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
         final int device = btInfo.mAudioSystemDevice;
 
@@ -2234,7 +2371,7 @@
 
             AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
             final int res = AudioSystem.setDeviceConnectionState(ada,
-                    AudioSystem.DEVICE_STATE_AVAILABLE,  AudioSystem.AUDIO_FORMAT_DEFAULT);
+                    AudioSystem.DEVICE_STATE_AVAILABLE, codec);
             if (res != AudioSystem.AUDIO_STATUS_OK) {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "APM failed to make available LE Audio device addr=" + address
@@ -2249,11 +2386,12 @@
             // Reset LEA suspend state each time a new sink is connected
             mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
-                    new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT,
+                    new DeviceInfo(device, name, address, codec,
                             peerAddress, groupId));
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
-            addAudioDeviceInInventoryIfNeeded(device, address, peerAddress);
+            addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
+                    BtHelper.getBtDeviceCategory(address));
         }
 
         if (streamType == AudioSystem.STREAM_DEFAULT) {
@@ -2272,13 +2410,14 @@
     }
 
     @GuardedBy("mDevicesLock")
-    private void makeLeAudioDeviceUnavailableNow(String address, int device) {
+    private void makeLeAudioDeviceUnavailableNow(String address, int device,
+            @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) {
         AudioDeviceAttributes ada = null;
         if (device != AudioSystem.DEVICE_NONE) {
             ada = new AudioDeviceAttributes(device, address);
             final int res = AudioSystem.setDeviceConnectionState(ada,
                     AudioSystem.DEVICE_STATE_UNAVAILABLE,
-                    AudioSystem.AUDIO_FORMAT_DEFAULT);
+                    codec);
 
             if (res != AudioSystem.AUDIO_STATUS_OK) {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
@@ -2303,7 +2442,8 @@
     }
 
     @GuardedBy("mDevicesLock")
-    private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) {
+    private void makeLeAudioDeviceUnavailableLater(
+            String address, int device, int codec, int delayMs) {
         // prevent any activity on the LEA output to avoid unwanted
         // reconnection of the sink.
         mDeviceBroker.setLeAudioSuspended(
@@ -2311,7 +2451,7 @@
         // the device will be made unavailable later, so consider it disconnected right away
         mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
         // send the delayed message to make the device unavailable later
-        mDeviceBroker.setLeAudioTimeout(address, device, delayMs);
+        mDeviceBroker.setLeAudioTimeout(address, device, codec, delayMs);
     }
 
     @GuardedBy("mDevicesLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 4f6c6d6..f149636 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED;
 import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
 import static android.media.audio.Flags.autoPublicVolumeApiHardening;
+import static android.media.audio.Flags.automaticBtDeviceType;
 import static android.media.audio.Flags.focusFreezeTestApi;
 import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
 import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
@@ -11148,9 +11149,13 @@
 
     @Override
     @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
-    public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle,
+    public void setBluetoothAudioDeviceCategory_legacy(@NonNull String address, boolean isBle,
             @AudioDeviceCategory int btAudioDeviceCategory) {
-        super.setBluetoothAudioDeviceCategory_enforcePermission();
+        super.setBluetoothAudioDeviceCategory_legacy_enforcePermission();
+        if (automaticBtDeviceType()) {
+            // do nothing
+            return;
+        }
 
         final String addr = Objects.requireNonNull(address);
 
@@ -11182,8 +11187,11 @@
     @Override
     @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
     @AudioDeviceCategory
-    public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) {
-        super.getBluetoothAudioDeviceCategory_enforcePermission();
+    public int getBluetoothAudioDeviceCategory_legacy(@NonNull String address, boolean isBle) {
+        super.getBluetoothAudioDeviceCategory_legacy_enforcePermission();
+        if (automaticBtDeviceType()) {
+            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
 
         final AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(
                 Objects.requireNonNull(address), (isBle ? AudioSystem.DEVICE_OUT_BLE_HEADSET
@@ -11195,6 +11203,61 @@
         return deviceState.getAudioDeviceCategory();
     }
 
+    @Override
+    @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public boolean setBluetoothAudioDeviceCategory(@NonNull String address,
+            @AudioDeviceCategory int btAudioDeviceCategory) {
+        super.setBluetoothAudioDeviceCategory_enforcePermission();
+        if (!automaticBtDeviceType()) {
+            return false;
+        }
+
+        final String addr = Objects.requireNonNull(address);
+        if (isBluetoothAudioDeviceCategoryFixed(addr)) {
+            Log.w(TAG, "Cannot set fixed audio device type for address "
+                    + Utils.anonymizeBluetoothAddress(address));
+            return false;
+        }
+
+        mDeviceBroker.addAudioDeviceWithCategoryInInventoryIfNeeded(address, btAudioDeviceCategory);
+
+        return true;
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    @AudioDeviceCategory
+    public int getBluetoothAudioDeviceCategory(@NonNull String address) {
+        super.getBluetoothAudioDeviceCategory_enforcePermission();
+        if (!automaticBtDeviceType()) {
+            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
+
+        return mDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(address);
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    @AudioDeviceCategory
+    public boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) {
+        super.isBluetoothAudioDeviceCategoryFixed_enforcePermission();
+        if (!automaticBtDeviceType()) {
+            return false;
+        }
+
+        return mDeviceBroker.isBluetoothAudioDeviceCategoryFixed(address);
+    }
+
+    /*package*/void onUpdatedAdiDeviceState(AdiDeviceState deviceState) {
+        if (deviceState == null) {
+            return;
+        }
+        mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes());
+        mSoundDoseHelper.setAudioDeviceCategory(deviceState.getDeviceAddress(),
+                deviceState.getInternalDeviceType(),
+                deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES);
+    }
+
     //==========================================================================================
     // Hdmi CEC:
     // - System audio mode:
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 7b96215..401dc88 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -15,6 +15,22 @@
  */
 package com.android.server.audio;
 
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_CARKIT;
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DEFAULT;
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEADSET;
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEARING_AID;
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_SPEAKER;
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET;
+import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_WATCH;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_RECEIVER;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
+import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_WATCH;
+import static android.media.audio.Flags.automaticBtDeviceType;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothA2dp;
@@ -26,12 +42,14 @@
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothLeAudioCodecConfig;
 import android.bluetooth.BluetoothLeAudioCodecStatus;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioManager;
+import android.media.AudioManager.AudioDeviceCategory;
 import android.media.AudioSystem;
 import android.media.BluetoothProfileConnectionInfo;
 import android.os.Binder;
@@ -250,35 +268,73 @@
         }
     }
 
-    /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getA2dpCodec(
-            @NonNull BluetoothDevice device) {
-        if (mA2dp == null) {
-            return AudioSystem.AUDIO_FORMAT_DEFAULT;
+    /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec(
+            @NonNull BluetoothDevice device, @AudioService.BtProfile int profile) {
+        switch (profile) {
+            case BluetoothProfile.A2DP: {
+                if (mA2dp == null) {
+                    return AudioSystem.AUDIO_FORMAT_DEFAULT;
+                }
+                BluetoothCodecStatus btCodecStatus = null;
+                try {
+                    btCodecStatus = mA2dp.getCodecStatus(device);
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception while getting status of " + device, e);
+                }
+                if (btCodecStatus == null) {
+                    return AudioSystem.AUDIO_FORMAT_DEFAULT;
+                }
+                final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
+                if (btCodecConfig == null) {
+                    return AudioSystem.AUDIO_FORMAT_DEFAULT;
+                }
+                return AudioSystem.bluetoothA2dpCodecToAudioFormat(btCodecConfig.getCodecType());
+            }
+            case BluetoothProfile.LE_AUDIO: {
+                if (mLeAudio == null) {
+                    return AudioSystem.AUDIO_FORMAT_DEFAULT;
+                }
+                BluetoothLeAudioCodecStatus btLeCodecStatus = null;
+                int groupId = mLeAudio.getGroupId(device);
+                try {
+                    btLeCodecStatus = mLeAudio.getCodecStatus(groupId);
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception while getting status of " + device, e);
+                }
+                if (btLeCodecStatus == null) {
+                    return AudioSystem.AUDIO_FORMAT_DEFAULT;
+                }
+                BluetoothLeAudioCodecConfig btLeCodecConfig =
+                        btLeCodecStatus.getOutputCodecConfig();
+                if (btLeCodecConfig == null) {
+                    return AudioSystem.AUDIO_FORMAT_DEFAULT;
+                }
+                return AudioSystem.bluetoothLeCodecToAudioFormat(btLeCodecConfig.getCodecType());
+            }
+            default:
+                return AudioSystem.AUDIO_FORMAT_DEFAULT;
         }
-        BluetoothCodecStatus btCodecStatus = null;
-        try {
-            btCodecStatus = mA2dp.getCodecStatus(device);
-        } catch (Exception e) {
-            Log.e(TAG, "Exception while getting status of " + device, e);
-        }
-        if (btCodecStatus == null) {
-            return AudioSystem.AUDIO_FORMAT_DEFAULT;
-        }
-        final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
-        if (btCodecConfig == null) {
-            return AudioSystem.AUDIO_FORMAT_DEFAULT;
-        }
-        return AudioSystem.bluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
     }
 
     /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec
-            int getA2dpCodecWithFallbackToSBC(
-                    @NonNull BluetoothDevice device, @NonNull String source) {
-        @AudioSystem.AudioFormatNativeEnumForBtCodec int codec = getA2dpCodec(device);
+            int getCodecWithFallback(
+                    @NonNull BluetoothDevice device, @AudioService.BtProfile int profile,
+                    boolean isLeOutput, @NonNull String source) {
+        // For profiles other than A2DP and LE Audio output, the audio codec format must be
+        // AUDIO_FORMAT_DEFAULT as native audio policy manager expects a specific audio format
+        // only if audio HW module selection based on format is supported for the device type.
+        if (!(profile == BluetoothProfile.A2DP
+                || (profile == BluetoothProfile.LE_AUDIO && isLeOutput))) {
+            return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+        @AudioSystem.AudioFormatNativeEnumForBtCodec int codec =
+                getCodec(device, profile);
         if (codec == AudioSystem.AUDIO_FORMAT_DEFAULT) {
             AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                    "getA2dpCodec DEFAULT from " + source + " fallback to SBC"));
-            return AudioSystem.AUDIO_FORMAT_SBC;
+                    "getCodec DEFAULT from " + source + " fallback to "
+                            + (profile == BluetoothProfile.A2DP ? "SBC" : "LC3")));
+            return profile == BluetoothProfile.A2DP
+                    ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3;
         }
         return codec;
     }
@@ -1076,6 +1132,71 @@
         return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address));
     }
 
+    @Nullable
+    /*package */ static BluetoothDevice getBluetoothDevice(String address) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter == null || !BluetoothAdapter.checkBluetoothAddress(address)) {
+            return null;
+        }
+
+        return adapter.getRemoteDevice(address);
+    }
+
+    @AudioDeviceCategory
+    /*package*/ static int getBtDeviceCategory(String address) {
+        if (!automaticBtDeviceType()) {
+            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
+
+        BluetoothDevice device = BtHelper.getBluetoothDevice(address);
+        if (device == null) {
+            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
+
+        byte[] deviceType = device.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE);
+        if (deviceType == null) {
+            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
+        String deviceCategory = new String(deviceType);
+        switch (deviceCategory) {
+            case DEVICE_TYPE_HEARING_AID:
+                return AUDIO_DEVICE_CATEGORY_HEARING_AID;
+            case DEVICE_TYPE_CARKIT:
+                return AUDIO_DEVICE_CATEGORY_CARKIT;
+            case DEVICE_TYPE_HEADSET:
+            case DEVICE_TYPE_UNTETHERED_HEADSET:
+                return AUDIO_DEVICE_CATEGORY_HEADPHONES;
+            case DEVICE_TYPE_SPEAKER:
+                return AUDIO_DEVICE_CATEGORY_SPEAKER;
+            case DEVICE_TYPE_WATCH:
+                return AUDIO_DEVICE_CATEGORY_WATCH;
+            case DEVICE_TYPE_DEFAULT:
+            default:
+                // fall through
+        }
+
+        BluetoothClass deviceClass = device.getBluetoothClass();
+        if (deviceClass == null) {
+            return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
+
+        switch (deviceClass.getDeviceClass()) {
+            case BluetoothClass.Device.WEARABLE_WRIST_WATCH:
+                return AUDIO_DEVICE_CATEGORY_WATCH;
+            case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER:
+            case BluetoothClass.Device.AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER:
+            case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
+                return AUDIO_DEVICE_CATEGORY_SPEAKER;
+            case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+            case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
+                return AUDIO_DEVICE_CATEGORY_HEADPHONES;
+            case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
+                return AUDIO_DEVICE_CATEGORY_RECEIVER;
+            default:
+                return AUDIO_DEVICE_CATEGORY_UNKNOWN;
+        }
+    }
+
     /**
      * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices
      * have been applied.
diff --git a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
index bbe819f..9b0afc4 100644
--- a/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
+++ b/services/core/java/com/android/server/audio/LoudnessCodecHelper.java
@@ -26,10 +26,12 @@
 import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
 import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
 import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
+import static android.media.audio.Flags.automaticBtDeviceType;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.media.AudioDeviceInfo;
+import android.media.AudioManager.AudioDeviceCategory;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioSystem;
 import android.media.ILoudnessCodecUpdatesDispatcher;
@@ -552,6 +554,13 @@
     @DeviceSplRange
     private int getDeviceSplRange(AudioDeviceInfo deviceInfo) {
         final int internalDeviceType = deviceInfo.getInternalType();
+        final @AudioDeviceCategory int deviceCategory;
+        if (automaticBtDeviceType()) {
+            deviceCategory = mAudioService.getBluetoothAudioDeviceCategory(deviceInfo.getAddress());
+        } else {
+            deviceCategory = mAudioService.getBluetoothAudioDeviceCategory_legacy(
+                    deviceInfo.getAddress(), AudioSystem.isBluetoothLeDevice(internalDeviceType));
+        }
         if (internalDeviceType == AudioSystem.DEVICE_OUT_SPEAKER) {
             final String splRange = SystemProperties.get(
                     SYSTEM_PROPERTY_SPEAKER_SPL_RANGE_SIZE, "unknown");
@@ -569,18 +578,14 @@
                 || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
                 || internalDeviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET
                 || (AudioSystem.isBluetoothDevice(internalDeviceType)
-                && mAudioService.getBluetoothAudioDeviceCategory(deviceInfo.getAddress(),
-                AudioSystem.isBluetoothLeDevice(internalDeviceType))
-                == AUDIO_DEVICE_CATEGORY_HEADPHONES)) {
+                && deviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES)) {
             return SPL_RANGE_LARGE;
         } else if (AudioSystem.isBluetoothDevice(internalDeviceType)) {
-            final int audioDeviceType = mAudioService.getBluetoothAudioDeviceCategory(
-                    deviceInfo.getAddress(), AudioSystem.isBluetoothLeDevice(internalDeviceType));
-            if (audioDeviceType == AUDIO_DEVICE_CATEGORY_CARKIT) {
+            if (deviceCategory == AUDIO_DEVICE_CATEGORY_CARKIT) {
                 return SPL_RANGE_MEDIUM;
-            } else if (audioDeviceType == AUDIO_DEVICE_CATEGORY_WATCH) {
+            } else if (deviceCategory == AUDIO_DEVICE_CATEGORY_WATCH) {
                 return SPL_RANGE_SMALL;
-            } else if (audioDeviceType == AUDIO_DEVICE_CATEGORY_HEARING_AID) {
+            } else if (deviceCategory == AUDIO_DEVICE_CATEGORY_HEARING_AID) {
                 return SPL_RANGE_SMALL;
             }
         }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index f09fcea..2cca72e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1978,8 +1978,9 @@
                     || sdrAnimateValue != currentSdrBrightness)) {
                 boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
                         || !isDisplayContentVisible || brightnessIsTemporary;
-                if (!skipAnimation && BrightnessSynchronizer.floatEquals(
-                        sdrAnimateValue, currentSdrBrightness)) {
+                final boolean isHdrOnlyChange = BrightnessSynchronizer.floatEquals(
+                        sdrAnimateValue, currentSdrBrightness);
+                if (mFlags.isFastHdrTransitionsEnabled() && !skipAnimation && isHdrOnlyChange) {
                     // SDR brightness is unchanged, so animate quickly as this is only impacting
                     // a likely minority amount of display content
                     // ie, the highlights of an HDR video or UltraHDR image
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 5310e43..810ac08 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1602,8 +1602,9 @@
                     || sdrAnimateValue != currentSdrBrightness)) {
                 boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
                         || !isDisplayContentVisible || brightnessIsTemporary;
-                if (!skipAnimation && BrightnessSynchronizer.floatEquals(
-                        sdrAnimateValue, currentSdrBrightness)) {
+                final boolean isHdrOnlyChange = BrightnessSynchronizer.floatEquals(
+                        sdrAnimateValue, currentSdrBrightness);
+                if (mFlags.isFastHdrTransitionsEnabled() && !skipAnimation && isHdrOnlyChange) {
                     // SDR brightness is unchanged, so animate quickly as this is only impacting
                     // a likely minority amount of display content
                     // ie, the highlights of an HDR video or UltraHDR image
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 f579dbd..bd5e189 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -101,6 +101,10 @@
             Flags.FLAG_AUTO_BRIGHTNESS_MODES,
             Flags::autoBrightnessModes);
 
+    private final FlagState mFastHdrTransitions = new FlagState(
+            Flags.FLAG_FAST_HDR_TRANSITIONS,
+            Flags::fastHdrTransitions);
+
     /** Returns whether connected display management is enabled or not. */
     public boolean isConnectedDisplayManagementEnabled() {
         return mConnectedDisplayManagementFlagState.isEnabled();
@@ -205,6 +209,10 @@
         return mAutoBrightnessModesFlagState.isEnabled();
     }
 
+    public boolean isFastHdrTransitionsEnabled() {
+        return mFastHdrTransitions.isEnabled();
+    }
+
     /**
      * dumps all flagstates
      * @param pw printWriter
@@ -226,6 +234,7 @@
         pw.println(" " + mVsyncProximityVote);
         pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState);
         pw.println(" " + mAutoBrightnessModesFlagState);
+        pw.println(" " + mFastHdrTransitions);
     }
 
     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 1b4d74c..7a723a3 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
@@ -144,3 +144,12 @@
     bug: "293613040"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "fast_hdr_transitions"
+    namespace: "display_manager"
+    description: "Feature flag for fast transitions into/out of HDR"
+    bug: "292124102"
+    is_fixed_read_only: true
+}
+
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 2533e02..3fc9594 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -295,6 +295,8 @@
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
     private final AdditionalDisplayInputProperties mCurrentDisplayProperties =
             new AdditionalDisplayInputProperties();
+    // TODO(b/293587049): Pointer Icon Refactor: There can be more than one pointer icon
+    // visible at once. Update this to support multi-pointer use cases.
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
     private int mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED;
     @GuardedBy("mAdditionalDisplayInputPropertiesLock")
@@ -1756,6 +1758,21 @@
         }
     }
 
+    // Binder call
+    @Override
+    public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
+            IBinder inputToken) {
+        Objects.requireNonNull(icon);
+        synchronized (mAdditionalDisplayInputPropertiesLock) {
+            mPointerIconType = icon.getType();
+            mPointerIcon = mPointerIconType == PointerIcon.TYPE_CUSTOM ? icon : null;
+
+            if (!mCurrentDisplayProperties.pointerIconVisible) return false;
+
+            return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
+        }
+    }
+
     /**
      * Add a runtime association between the input port and the display port. This overrides any
      * static associations.
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index f126a89..620cde5 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -186,6 +186,9 @@
 
     void setCustomPointerIcon(PointerIcon icon);
 
+    boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
+            IBinder inputToken);
+
     void requestPointerCapture(IBinder windowToken, boolean enabled);
 
     boolean canDispatchToDisplay(int deviceId, int displayId);
@@ -434,6 +437,10 @@
         public native void setCustomPointerIcon(PointerIcon icon);
 
         @Override
+        public native boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId,
+                int pointerId, IBinder inputToken);
+
+        @Override
         public native void requestPointerCapture(IBinder windowToken, boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index a0bc7c2..98f627c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -741,7 +741,6 @@
      */
     int mImeWindowVis;
 
-    private LocaleList mLastSystemLocales;
     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
     private final String mSlotIme;
 
@@ -1199,9 +1198,6 @@
             if (Intent.ACTION_USER_ADDED.equals(action)
                     || Intent.ACTION_USER_REMOVED.equals(action)) {
                 updateCurrentProfileIds();
-                return;
-            } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
-                onActionLocaleChanged();
             } else {
                 Slog.w(TAG, "Unexpected intent " + intent);
             }
@@ -1240,20 +1236,19 @@
      *
      * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
      * the users. We should ignore this event if this is about any background user's locale.</p>
-     *
-     * <p>Caution: This method must not be called when system is not ready.</p>
      */
-    void onActionLocaleChanged() {
+    void onActionLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales) {
+        if (DEBUG) {
+            Slog.d(TAG, "onActionLocaleChanged prev=" + prevLocales + " new=" + newLocales);
+        }
         synchronized (ImfLock.class) {
-            final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales();
-            if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) {
+            if (!mSystemReady) {
                 return;
             }
             buildInputMethodListLocked(true);
             // If the locale is changed, needs to reset the default ime
             resetDefaultImeLocked(mContext);
             updateFromSettingsLocked(true);
-            mLastSystemLocales = possibleNewLocale;
         }
     }
 
@@ -1681,6 +1676,7 @@
                                 true /* allowIo */);
         thread.start();
         mHandler = Handler.createAsync(thread.getLooper(), this);
+        SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
         mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
                 ? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
         // Note: SettingsObserver doesn't register observers in its constructor.
@@ -1838,7 +1834,6 @@
         // Even in such cases, IMMS works fine because it will find the most applicable
         // IME for that user.
         final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
-        mLastSystemLocales = mRes.getConfiguration().getLocales();
 
         // The mSystemReady flag is set during boot phase,
         // and user switch would not happen at that time.
@@ -1890,7 +1885,6 @@
             }
             if (!mSystemReady) {
                 mSystemReady = true;
-                mLastSystemLocales = mRes.getConfiguration().getLocales();
                 final int currentUserId = mSettings.getCurrentUserId();
                 mSettings.switchCurrentUser(currentUserId,
                         !mUserManagerInternal.isUserUnlockingOrUnlocked(currentUserId));
@@ -1930,7 +1924,6 @@
                 final IntentFilter broadcastFilterForSystemUser = new IntentFilter();
                 broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_ADDED);
                 broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_REMOVED);
-                broadcastFilterForSystemUser.addAction(Intent.ACTION_LOCALE_CHANGED);
                 mContext.registerReceiver(new ImmsBroadcastReceiverForSystemUser(),
                         broadcastFilterForSystemUser);
 
@@ -4073,14 +4066,19 @@
                 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
                 if (enabled != null) {
                     final int enabledCount = enabled.size();
-                    final String locale = mCurrentSubtype == null
-                            ? mRes.getConfiguration().locale.toString()
-                            : mCurrentSubtype.getLocale();
+                    final String locale;
+                    if (mCurrentSubtype != null
+                            && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
+                        locale = mCurrentSubtype.getLocale();
+                    } else {
+                        locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId()).get(0)
+                                .toString();
+                    }
                     for (int i = 0; i < enabledCount; ++i) {
                         final InputMethodInfo imi = enabled.get(i);
                         if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
                             InputMethodSubtype keyboardSubtype =
-                                    SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes,
+                                    SubtypeUtils.findLastResortApplicableSubtypeLocked(
                                             SubtypeUtils.getSubtypes(imi),
                                             SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
                             if (keyboardSubtype != null) {
@@ -5430,12 +5428,14 @@
                 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
                     mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
                 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
+                    final String locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId())
+                            .get(0).toString();
                     mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
-                            mRes, explicitlyOrImplicitlyEnabledSubtypes,
-                            SubtypeUtils.SUBTYPE_MODE_KEYBOARD, null, true);
+                            explicitlyOrImplicitlyEnabledSubtypes,
+                            SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
                     if (mCurrentSubtype == null) {
                         mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
-                                mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, true);
+                                explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
                     }
                 }
             } else {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 984ae1f..c661c86 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -29,6 +29,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.Build;
+import android.os.LocaleList;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -218,7 +219,6 @@
 
         @NonNull
         private Context mUserAwareContext;
-        private Resources mRes;
         private ContentResolver mResolver;
         private final ArrayMap<String, InputMethodInfo> mMethodMap;
 
@@ -281,7 +281,6 @@
             mUserAwareContext = context.getUserId() == userId
                     ? context
                     : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
-            mRes = mUserAwareContext.getResources();
             mResolver = mUserAwareContext.getContentResolver();
         }
 
@@ -397,7 +396,8 @@
             List<InputMethodSubtype> enabledSubtypes =
                     getEnabledInputMethodSubtypeListLocked(imi);
             if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
-                enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi);
+                enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+                        SystemLocaleWrapper.get(mCurrentUserId), imi);
             }
             return InputMethodSubtype.sort(imi, enabledSubtypes);
         }
@@ -646,6 +646,7 @@
 
         private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
                 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
+            final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
             for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
                 if (enabledIme.first.equals(imeId)) {
                     final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
@@ -657,7 +658,8 @@
                         // are enabled implicitly, so needs to treat them to be enabled.
                         if (imi != null && imi.getSubtypeCount() > 0) {
                             List<InputMethodSubtype> implicitlyEnabledSubtypes =
-                                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi);
+                                    SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
+                                            imi);
                             final int numSubtypes = implicitlyEnabledSubtypes.size();
                             for (int i = 0; i < numSubtypes; ++i) {
                                 final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
@@ -847,14 +849,15 @@
             if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
                 return explicitlyOrImplicitlyEnabledSubtypes.get(0);
             }
+            final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
             final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
-                    mRes, explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
-                    null, true);
+                    explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
+                    locale, true);
             if (subtype != null) {
                 return subtype;
             }
-            return SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes,
-                    explicitlyOrImplicitlyEnabledSubtypes, null, null, true);
+            return SubtypeUtils.findLastResortApplicableSubtypeLocked(
+                    explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
         }
 
         boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
index 0185190..95df998 100644
--- a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.res.Resources;
 import android.os.LocaleList;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -125,9 +124,7 @@
     @VisibleForTesting
     @NonNull
     static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
-            Resources res, InputMethodInfo imi) {
-        final LocaleList systemLocales = res.getConfiguration().getLocales();
-
+            @NonNull LocaleList systemLocales, InputMethodInfo imi) {
         synchronized (sCacheLock) {
             // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because
             // it does not check if subtypes are also identical.
@@ -140,7 +137,7 @@
         // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive
         // LocaleList rather than Resource.
         final ArrayList<InputMethodSubtype> result =
-                getImplicitlyApplicableSubtypesLockedImpl(res, imi);
+                getImplicitlyApplicableSubtypesLockedImpl(systemLocales, imi);
         synchronized (sCacheLock) {
             // Both LocaleList and InputMethodInfo are immutable. No need to copy them here.
             sCachedSystemLocales = systemLocales;
@@ -151,9 +148,8 @@
     }
 
     private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl(
-            Resources res, InputMethodInfo imi) {
+            @NonNull LocaleList systemLocales, InputMethodInfo imi) {
         final List<InputMethodSubtype> subtypes = getSubtypes(imi);
-        final LocaleList systemLocales = res.getConfiguration().getLocales();
         final String systemLocale = systemLocales.get(0).toString();
         if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>();
         final int numSubtypes = subtypes.size();
@@ -220,7 +216,7 @@
 
         if (applicableSubtypes.isEmpty()) {
             InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
-                    res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
+                    subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
             if (lastResortKeyboardSubtype != null) {
                 applicableSubtypes.add(lastResortKeyboardSubtype);
             }
@@ -249,14 +245,11 @@
      * @return the most applicable subtypeId
      */
     static InputMethodSubtype findLastResortApplicableSubtypeLocked(
-            Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
+            List<InputMethodSubtype> subtypes, String mode, @NonNull String locale,
             boolean canIgnoreLocaleAsLastResort) {
         if (subtypes == null || subtypes.isEmpty()) {
             return null;
         }
-        if (TextUtils.isEmpty(locale)) {
-            locale = res.getConfiguration().locale.toString();
-        }
         final String language = LocaleUtils.getLanguageFromLocaleString(locale);
         boolean partialMatchFound = false;
         InputMethodSubtype applicableSubtype = null;
diff --git a/services/core/java/com/android/server/inputmethod/SystemLocaleWrapper.java b/services/core/java/com/android/server/inputmethod/SystemLocaleWrapper.java
new file mode 100644
index 0000000..0f1b711
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SystemLocaleWrapper.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.LocaleList;
+
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A set of thread-safe utility methods for the system locals.
+ */
+final class SystemLocaleWrapper {
+    /**
+     * Not intended to be instantiated.
+     */
+    private SystemLocaleWrapper() {
+    }
+
+    private static final AtomicReference<LocaleList> sSystemLocale =
+            new AtomicReference<>(new LocaleList(Locale.getDefault()));
+
+    /**
+     * Returns {@link LocaleList} for the specified user.
+     *
+     * <p>Note: If you call this method twice, it is possible that the second value is different
+     * from the first value. The caller is responsible for taking care of such cases.</p>
+     *
+     * @param userId the ID of the user to query about.
+     * @return {@link LocaleList} associated with the user.
+     */
+    @AnyThread
+    @NonNull
+    static LocaleList get(@UserIdInt int userId) {
+        // Currently system locale is not per-user.
+        // TODO(b/30119489): Make this per-user.
+        return sSystemLocale.get();
+    }
+
+    /**
+     * Callback for the locale change event. When this gets filed, {@link #get(int)} is already
+     * updated to return the new value.
+     */
+    interface Callback {
+        void onLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales);
+    }
+
+    /**
+     * Called when {@link InputMethodManagerService} is about to start.
+     *
+     * @param context {@link Context} to be used.
+     * @param callback {@link Callback} for the locale change events.
+     */
+    @AnyThread
+    static void onStart(@NonNull Context context, @NonNull Callback callback,
+            @NonNull Handler handler) {
+        sSystemLocale.set(context.getResources().getConfiguration().getLocales());
+
+        context.registerReceiver(new LocaleChangeListener(context, callback),
+                new IntentFilter(Intent.ACTION_LOCALE_CHANGED), null, handler);
+    }
+
+    private static final class LocaleChangeListener extends BroadcastReceiver {
+        @NonNull
+        private final Context mContext;
+        @NonNull
+        private final Callback mCallback;
+        LocaleChangeListener(@NonNull Context context, @NonNull Callback callback) {
+            mContext = context;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            final LocaleList newLocales = mContext.getResources().getConfiguration().getLocales();
+            final LocaleList prevLocales = sSystemLocale.getAndSet(newLocales);
+            if (!Objects.equals(newLocales, prevLocales)) {
+                mCallback.onLocaleChanged(prevLocales, newLocales);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index 1641d4a..87158cd 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -23,6 +23,7 @@
 import static android.service.notification.NotificationServiceProto.RULE_TYPE_UNKNOWN;
 
 import android.annotation.NonNull;
+import android.app.Flags;
 import android.app.NotificationManager;
 import android.content.pm.PackageManager;
 import android.os.Process;
@@ -502,6 +503,13 @@
                         ZenModeConfig.getZenPolicySenders(mNewPolicy.allowMessagesFrom()));
                 proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM,
                         mNewPolicy.allowConversationsFrom());
+
+                if (Flags.modesApi()) {
+                    proto.write(DNDPolicyProto.ALLOW_CHANNELS,
+                            mNewPolicy.allowPriorityChannels()
+                                    ? ZenPolicy.CHANNEL_TYPE_PRIORITY
+                                    : ZenPolicy.CHANNEL_TYPE_NONE);
+                }
             } else {
                 Log.wtf(TAG, "attempted to write zen mode log event with null policy");
             }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 89d8200..d0ded63 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1519,7 +1519,7 @@
         final boolean muteEverything = zenSilence || (zenPriorityOnly
                 && ZenModeConfig.areAllZenBehaviorSoundsMuted(mConsolidatedPolicy));
 
-        for (int usage : AudioAttributes.SDK_USAGES) {
+        for (int usage : AudioAttributes.SDK_USAGES.toArray()) {
             final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
             if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NEVER) {
                 applyRestrictions(zenPriorityOnly, false /*mute*/, usage);
diff --git a/services/core/java/com/android/server/pdb/TEST_MAPPING b/services/core/java/com/android/server/pdb/TEST_MAPPING
deleted file mode 100644
index e5f154a..0000000
--- a/services/core/java/com/android/server/pdb/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-    "postsubmit": [
-        {
-            "name": " PersistentDataBlockServiceTest"
-        }
-    ]
-}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index e5c4ccc..b2d4a2c 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1500,15 +1500,14 @@
                     state.getFirstInstallTimeMillis(), ps.getLastUpdateTime(), installedPermissions,
                     grantedPermissions, state, userId, ps);
 
-            if (packageInfo == null) {
-                return null;
+            if (packageInfo != null) {
+                packageInfo.packageName = packageInfo.applicationInfo.packageName =
+                        resolveExternalPackageName(p);
+                return packageInfo;
             }
-
-            packageInfo.packageName = packageInfo.applicationInfo.packageName =
-                    resolveExternalPackageName(p);
-
-            return packageInfo;
-        } else if ((flags & (MATCH_UNINSTALLED_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0
+        }
+        // TODO(b/314808978): Set ps.setPkg to null during install-archived.
+        if ((flags & (MATCH_UNINSTALLED_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0
                 && PackageUserStateUtils.isAvailable(state, flags)) {
             PackageInfo pi = new PackageInfo();
             pi.packageName = ps.getPackageName();
@@ -1540,9 +1539,8 @@
                         + ps.getPackageName() + "]. Provides a minimum info.");
             }
             return pi;
-        } else {
-            return null;
         }
+        return null;
     }
 
     public final PackageInfo getPackageInfo(String packageName,
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 1a65297..3e7c8c4 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2158,7 +2158,11 @@
                 }
             }
             if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
-                mPm.createArchiveStateIfNeeded(ps,
+                // If this is an archival installation then we'll initialize the archive status,
+                // while also marking package as not installed.
+                // Doing this at the very end of the install as we are using ps.getInstalled
+                // to figure out which users were changed.
+                mPm.markPackageAsArchivedIfNeeded(ps,
                         installRequest.getArchivedPackage(),
                         installRequest.getNewUsers());
                 mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 9f072f9..e3bab3f 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -54,6 +54,7 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.DeleteFlags;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.VersionedPackage;
@@ -154,7 +155,8 @@
             @NonNull String packageName,
             @NonNull String callerPackageName,
             @NonNull IntentSender intentSender,
-            @NonNull UserHandle userHandle) {
+            @NonNull UserHandle userHandle,
+            @DeleteFlags int flags) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(callerPackageName);
         Objects.requireNonNull(intentSender);
@@ -195,7 +197,7 @@
                                     new VersionedPackage(packageName,
                                             PackageManager.VERSION_CODE_HIGHEST),
                                     callerPackageName,
-                                    DELETE_ARCHIVE | DELETE_KEEP_DATA,
+                                    DELETE_ARCHIVE | DELETE_KEEP_DATA | flags,
                                     intentSender,
                                     userId,
                                     binderUid);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index f731f95..7d6dd62 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -67,6 +67,7 @@
 import android.content.pm.PackageInstaller.UnarchivalStatus;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.DeleteFlags;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.VersionedPackage;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
@@ -1390,11 +1391,14 @@
         final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
                 statusReceiver, versionedPackage.getPackageName(),
                 canSilentlyInstallPackage, userId);
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+        final boolean shouldShowConfirmationDialog =
+                (flags & PackageManager.DELETE_SHOW_DIALOG) != 0;
+        if (!shouldShowConfirmationDialog
+                && mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
                     == PackageManager.PERMISSION_GRANTED) {
             // Sweet, call straight through!
             mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
-        } else if (canSilentlyInstallPackage) {
+        } else if (!shouldShowConfirmationDialog && canSilentlyInstallPackage) {
             // Allow the device owner and affiliated profile owner to silently delete packages
             // Need to clear the calling identity to get DELETE_PACKAGES permission
             final long ident = Binder.clearCallingIdentity();
@@ -1419,6 +1423,11 @@
             intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
             intent.putExtra(PackageInstaller.EXTRA_CALLBACK,
                     new PackageManager.UninstallCompleteCallback(adapter.getBinder().asBinder()));
+            if ((flags & PackageManager.DELETE_ARCHIVE) != 0) {
+                // Delete flags are passed to the uninstaller activity so it can be preserved
+                // in the follow-up uninstall operation after the user confirmation
+                intent.putExtra(PackageInstaller.EXTRA_DELETE_FLAGS, flags);
+            }
             adapter.onUserActionRequired(intent);
         }
     }
@@ -1631,9 +1640,10 @@
             @NonNull String packageName,
             @NonNull String callerPackageName,
             @NonNull IntentSender intentSender,
-            @NonNull UserHandle userHandle) {
+            @NonNull UserHandle userHandle,
+            @DeleteFlags int flags) {
         mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender,
-                userHandle);
+                userHandle, flags);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5daada9..c0c98de 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1518,8 +1518,8 @@
         return archPkg;
     }
 
-    void createArchiveStateIfNeeded(PackageSetting pkgSetting, ArchivedPackageParcel archivePackage,
-            int[] userIds) {
+    void markPackageAsArchivedIfNeeded(PackageSetting pkgSetting,
+                                       ArchivedPackageParcel archivePackage, int[] userIds) {
         if (pkgSetting == null || archivePackage == null
                 || archivePackage.archivedActivities == null || userIds == null
                 || userIds.length == 0) {
@@ -1541,6 +1541,7 @@
             }
             pkgSetting
                     .modifyUserState(userId)
+                    .setInstalled(false)
                     .setArchiveState(archiveState);
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index fc66203..215e952 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4639,7 +4639,7 @@
         try {
             mInterface.getPackageInstaller().requestArchive(packageName,
                     /* callerPackageName= */ "", receiver.getIntentSender(),
-                    new UserHandle(translatedUserId));
+                    new UserHandle(translatedUserId), 0);
         } catch (Exception e) {
             pw.println("Failure [" + e.getMessage() + "]");
             return 1;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1393121..b53a21c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5137,12 +5137,6 @@
             mPm.createNewUser(userId, userTypeInstallablePackages, disallowedPackages);
             t.traceEnd();
 
-            userInfo.partial = false;
-            synchronized (mPackagesLock) {
-                writeUserLP(userData);
-            }
-            updateUserIds();
-
             Bundle restrictions = new Bundle();
             if (isGuest) {
                 // Guest default restrictions can be modified via setDefaultGuestRestrictions.
@@ -5160,6 +5154,12 @@
                 mBaseUserRestrictions.updateRestrictions(userId, restrictions);
             }
 
+            userInfo.partial = false;
+            synchronized (mPackagesLock) {
+                writeUserLP(userData);
+            }
+            updateUserIds();
+
             t.traceBegin("PM.onNewUserCreated-" + userId);
             mPm.onNewUserCreated(userId, /* convertedFromPreCreated= */ false);
             t.traceEnd();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9610d05..d3931a3 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -278,8 +278,11 @@
     private boolean setAutoRevokeExemptedInternal(@NonNull AndroidPackage pkg, boolean exempted,
             @UserIdInt int userId) {
         final int packageUid = UserHandle.getUid(userId, pkg.getUid());
+        final AttributionSource attributionSource =
+                new AttributionSource(packageUid, pkg.getPackageName(), null);
+
         if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_AUTO_REVOKE_MANAGED_BY_INSTALLER,
-                packageUid, pkg.getPackageName()) != MODE_ALLOWED) {
+                attributionSource) != MODE_ALLOWED) {
             // Allowlist user set - don't override
             return false;
         }
@@ -330,8 +333,10 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
+            final AttributionSource attributionSource =
+                    new AttributionSource(packageUid, packageName, null);
             return mAppOpsManager.checkOpNoThrow(
-                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageUid, packageName)
+                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, attributionSource)
                     == MODE_IGNORED;
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -1157,9 +1162,11 @@
                     if (resolvedPackageName == null) {
                         return;
                     }
+                    final AttributionSource resolvedAccessorSource =
+                            accessorSource.withPackageName(resolvedPackageName);
+
                     appOpsManager.finishOp(attributionSourceState.token, op,
-                            accessorSource.getUid(), resolvedPackageName,
-                            accessorSource.getAttributionTag());
+                            resolvedAccessorSource);
                 } else {
                     final AttributionSource resolvedAttributionSource =
                             resolveAttributionSource(context, accessorSource);
@@ -1583,16 +1590,19 @@
                 if (resolvedAccessorPackageName == null) {
                     return AppOpsManager.MODE_ERRORED;
                 }
+                final AttributionSource resolvedAttributionSource =
+                        accessorSource.withPackageName(resolvedAccessorPackageName);
                 final int opMode = appOpsManager.unsafeCheckOpRawNoThrow(op,
-                        accessorSource.getUid(), resolvedAccessorPackageName);
+                        resolvedAttributionSource);
                 final AttributionSource next = accessorSource.getNext();
                 if (!selfAccess && opMode == AppOpsManager.MODE_ALLOWED && next != null) {
                     final String resolvedNextPackageName = resolvePackageName(context, next);
                     if (resolvedNextPackageName == null) {
                         return AppOpsManager.MODE_ERRORED;
                     }
-                    return appOpsManager.unsafeCheckOpRawNoThrow(op, next.getUid(),
-                            resolvedNextPackageName);
+                    final AttributionSource resolvedNextAttributionSource =
+                            next.withPackageName(resolvedNextPackageName);
+                    return appOpsManager.unsafeCheckOpRawNoThrow(op, resolvedNextAttributionSource);
                 }
                 return opMode;
             } else if (startDataDelivery) {
@@ -1615,9 +1625,7 @@
                 // the operation. We return the less permissive of the two and check
                 // the permission op while start the attributed op.
                 if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
-                    checkedOpResult = appOpsManager.checkOpNoThrow(op,
-                            resolvedAttributionSource.getUid(), resolvedAttributionSource
-                                    .getPackageName());
+                    checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource);
                     if (checkedOpResult == MODE_ERRORED) {
                         return checkedOpResult;
                     }
@@ -1626,12 +1634,9 @@
                 if (selfAccess) {
                     try {
                         startedOpResult = appOpsManager.startOpNoThrow(
-                                chainStartToken, startedOp,
-                                resolvedAttributionSource.getUid(),
-                                resolvedAttributionSource.getPackageName(),
-                                /*startIfModeDefault*/ false,
-                                resolvedAttributionSource.getAttributionTag(),
-                                message, proxyAttributionFlags, attributionChainId);
+                                chainStartToken, startedOp, resolvedAttributionSource,
+                                /*startIfModeDefault*/ false, message, proxyAttributionFlags,
+                                attributionChainId);
                     } catch (SecurityException e) {
                         Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
                                 + " platform defined runtime permission "
@@ -1676,9 +1681,7 @@
                 // the operation. We return the less permissive of the two and check
                 // the permission op while start the attributed op.
                 if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
-                    checkedOpResult = appOpsManager.checkOpNoThrow(op,
-                            resolvedAttributionSource.getUid(), resolvedAttributionSource
-                                    .getPackageName());
+                    checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource);
                     if (checkedOpResult == MODE_ERRORED) {
                         return checkedOpResult;
                     }
@@ -1692,10 +1695,7 @@
                     // As a fallback we note a proxy op that blames the app and the datasource.
                     try {
                         notedOpResult = appOpsManager.noteOpNoThrow(notedOp,
-                                resolvedAttributionSource.getUid(),
-                                resolvedAttributionSource.getPackageName(),
-                                resolvedAttributionSource.getAttributionTag(),
-                                message);
+                                resolvedAttributionSource, message);
                     } catch (SecurityException e) {
                         Slog.w(LOG_TAG, "Datasource " + attributionSource + " protecting data with"
                                 + " platform defined runtime permission "
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
index fe80f74..4b3992e 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java
@@ -93,8 +93,8 @@
      * this object exists means that the package must be installed or has data on at least one user;
      * <li> If it is not installed but still has data (i.e., it was previously uninstalled with
      * {@link PackageManager#DELETE_KEEP_DATA}), return true if the caller requested
-     * {@link PackageManager#MATCH_UNINSTALLED_PACKAGES} or
-     * {@link PackageManager#MATCH_ARCHIVED_PACKAGES};
+     * {@link PackageManager#MATCH_UNINSTALLED_PACKAGES}.
+     * Always available for {@link PackageManager#MATCH_ARCHIVED_PACKAGES}.
      * </ul><p>
      */
     public static boolean isAvailable(@NonNull PackageUserState state, long flags) {
@@ -109,11 +109,19 @@
         if (state.isInstalled()) {
             if (!state.isHidden()) {
                 return true;
-            } else return matchDataExists;
-        } else {
-            // not installed
-            return matchDataExists && state.dataExists();
+            } else {
+                return matchDataExists;
+            }
         }
+
+        // not installed
+        if (matchUninstalled) {
+            return state.dataExists();
+        }
+
+        // archived or installed as archived
+        // TODO(b/314808978): Create data folders during install-archived.
+        return matchArchived;
     }
 
     public static boolean reportIfDebug(boolean result, long flags) {
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index b83421f..ecffd38 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -50,11 +50,11 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.function.HeptFunction;
+import com.android.internal.util.function.DodecFunction;
+import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
+import com.android.internal.util.function.OctFunction;
 import com.android.internal.util.function.QuadFunction;
-import com.android.internal.util.function.QuintConsumer;
-import com.android.internal.util.function.QuintFunction;
 import com.android.internal.util.function.UndecFunction;
 import com.android.server.LocalServices;
 
@@ -230,9 +230,10 @@
 
     @Override
     public int checkOperation(int code, int uid, String packageName,
-            @Nullable String attributionTag, boolean raw,
-            QuintFunction<Integer, Integer, String, String, Boolean, Integer> superImpl) {
-        return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag, raw);
+            @Nullable String attributionTag, int virtualDeviceId, boolean raw,
+            HexFunction<Integer, Integer, String, String, Integer, Boolean, Integer> superImpl) {
+        return superImpl.apply(code, resolveUid(code, uid), packageName, attributionTag,
+                virtualDeviceId, raw);
     }
 
     @Override
@@ -243,12 +244,13 @@
 
     @Override
     public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName,
-            @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable
-            String message, boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer,
-                    String, String, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
+            @Nullable String attributionTag, int virtualDeviceId,
+            boolean shouldCollectAsyncNotedOp, @Nullable String message,
+            boolean shouldCollectMessage, @NonNull OctFunction<Integer, Integer, String, String,
+                    Integer, Boolean, String, Boolean, SyncNotedAppOp> superImpl) {
         return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag),
-                resolveUid(code, uid), packageName, attributionTag, shouldCollectAsyncNotedOp,
-                message, shouldCollectMessage);
+                resolveUid(code, uid), packageName, attributionTag, virtualDeviceId,
+                shouldCollectAsyncNotedOp, message, shouldCollectMessage);
     }
 
     @Override
@@ -265,16 +267,16 @@
 
     @Override
     public SyncNotedAppOp startOperation(IBinder token, int code, int uid,
-            @Nullable String packageName, @Nullable String attributionTag,
+            @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId,
             boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
             boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
-            int attributionChainId, @NonNull UndecFunction<IBinder, Integer, Integer, String,
-                    String, Boolean, Boolean, String, Boolean, Integer, Integer,
-            SyncNotedAppOp> superImpl) {
+            int attributionChainId, @NonNull DodecFunction<IBinder, Integer, Integer, String,
+                    String, Integer, Boolean, Boolean, String, Boolean, Integer, Integer,
+                    SyncNotedAppOp> superImpl) {
         return superImpl.apply(token, resolveDatasourceOp(code, uid, packageName, attributionTag),
-                resolveUid(code, uid), packageName, attributionTag, startIfModeDefault,
-                shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
-                attributionChainId);
+                resolveUid(code, uid), packageName, attributionTag, virtualDeviceId,
+                startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+                attributionFlags, attributionChainId);
     }
 
     @Override
@@ -294,10 +296,10 @@
 
     @Override
     public void finishOperation(IBinder clientId, int code, int uid, String packageName,
-            String attributionTag,
-            @NonNull QuintConsumer<IBinder, Integer, Integer, String, String> superImpl) {
+            String attributionTag, int virtualDeviceId,
+            @NonNull HexConsumer<IBinder, Integer, Integer, String, String, Integer> superImpl) {
         superImpl.accept(clientId, resolveDatasourceOp(code, uid, packageName, attributionTag),
-                resolveUid(code, uid), packageName, attributionTag);
+                resolveUid(code, uid), packageName, attributionTag, virtualDeviceId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
new file mode 100644
index 0000000..b531b0e
--- /dev/null
+++ b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.policy;
+
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class that is responsible for queueing deferred key actions which can be triggered at a later
+ * time.
+ */
+class DeferredKeyActionExecutor {
+    private static final boolean DEBUG = PhoneWindowManager.DEBUG_INPUT;
+    private static final String TAG = "DeferredKeyAction";
+
+    private final SparseArray<TimedActionsBuffer> mBuffers = new SparseArray<>();
+
+    /**
+     * Queue a key action which can be triggered at a later time. Note that this method will also
+     * delete any outdated actions belong to the same key code.
+     *
+     * <p>Warning: the queued actions will only be cleaned up lazily when a new gesture downTime is
+     * recorded. If no new gesture downTime is recorded and the existing gesture is not executable,
+     * the actions will be kept in the buffer indefinitely. This may cause memory leak if the action
+     * itself holds references to temporary objects, or if too many actions are queued for the same
+     * gesture. The risk scales as you track more key codes. Please use this method with caution and
+     * ensure you only queue small amount of actions with limited size.
+     *
+     * <p>If you need to queue a large amount of actions with large size, there are several
+     * potential solutions to relief the memory leak risks:
+     *
+     * <p>1. Add a timeout (e.g. ANR timeout) based clean-up mechanism.
+     *
+     * <p>2. Clean-up queued actions when we know they won't be needed. E.g., add a callback when
+     * the gesture is handled by apps, and clean up queued actions associated with the handled
+     * gesture.
+     *
+     * @param keyCode the key code which triggers the action.
+     * @param downTime the down time of the key gesture. For multi-press actions, this is the down
+     *     time of the last press. For long-press or very long-press actions, this is the initial
+     *     down time.
+     * @param action the action that will be triggered at a later time.
+     */
+    public void queueKeyAction(int keyCode, long downTime, Runnable action) {
+        getActionsBufferWithLazyCleanUp(keyCode, downTime).addAction(action);
+    }
+
+    /**
+     * Make actions associated with the given key gesture executable. Actions already queued for the
+     * given gesture will be executed immediately. Any new actions belonging to this gesture will be
+     * executed as soon as they get queued. Note that this method will also delete any outdated
+     * actions belong to the same key code.
+     *
+     * @param keyCode the key code of the gesture.
+     * @param downTime the down time of the gesture.
+     */
+    public void setActionsExecutable(int keyCode, long downTime) {
+        getActionsBufferWithLazyCleanUp(keyCode, downTime).setExecutable();
+    }
+
+    private TimedActionsBuffer getActionsBufferWithLazyCleanUp(int keyCode, long downTime) {
+        TimedActionsBuffer buffer = mBuffers.get(keyCode);
+        if (buffer == null || buffer.getDownTime() != downTime) {
+            if (DEBUG && buffer != null) {
+                Log.d(
+                        TAG,
+                        "getActionsBufferWithLazyCleanUp: cleaning up gesture actions for key "
+                                + KeyEvent.keyCodeToString(keyCode));
+            }
+            buffer = new TimedActionsBuffer(keyCode, downTime);
+            mBuffers.put(keyCode, buffer);
+        }
+        return buffer;
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "Deferred key action executor:");
+        if (mBuffers.size() == 0) {
+            pw.println(prefix + "  empty");
+            return;
+        }
+        for (int i = 0; i < mBuffers.size(); i++) {
+            mBuffers.valueAt(i).dump(prefix, pw);
+        }
+    }
+
+    /** A buffer holding a gesture down time and its corresponding actions. */
+    private static class TimedActionsBuffer {
+        private final List<Runnable> mActions = new ArrayList<>();
+        private final int mKeyCode;
+        private final long mDownTime;
+        private boolean mExecutable;
+
+        TimedActionsBuffer(int keyCode, long downTime) {
+            mKeyCode = keyCode;
+            mDownTime = downTime;
+        }
+
+        long getDownTime() {
+            return mDownTime;
+        }
+
+        void addAction(Runnable action) {
+            if (mExecutable) {
+                if (DEBUG) {
+                    Log.i(
+                            TAG,
+                            "addAction: execute action for key "
+                                    + KeyEvent.keyCodeToString(mKeyCode));
+                }
+                action.run();
+                return;
+            }
+            mActions.add(action);
+        }
+
+        void setExecutable() {
+            mExecutable = true;
+            if (DEBUG && !mActions.isEmpty()) {
+                Log.i(
+                        TAG,
+                        "setExecutable: execute actions for key "
+                                + KeyEvent.keyCodeToString(mKeyCode));
+            }
+            for (Runnable action : mActions) {
+                action.run();
+            }
+            mActions.clear();
+        }
+
+        void dump(String prefix, PrintWriter pw) {
+            if (mExecutable) {
+                pw.println(prefix + "  " + KeyEvent.keyCodeToString(mKeyCode) + ": executable");
+            } else {
+                pw.println(
+                        prefix
+                                + "  "
+                                + KeyEvent.keyCodeToString(mKeyCode)
+                                + ": "
+                                + mActions.size()
+                                + " actions queued");
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
index c2666f6..bb5a697 100644
--- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -206,22 +206,16 @@
         synchronized (mLock) {
             ClientState clientState = mClients.get(listener.asBinder());
 
-            if (clientState == null) {
-                if (DEBUG) {
-                    Slog.w(TAG, "#cancel called with no preceding #startListening - ignoring.");
-                }
-                return;
+            if (clientState != null) {
+                clientState.mRecordingInProgress = false;
+                // Temporary reference to allow for resetting mDelegatingListener to null.
+                final IRecognitionListener delegatingListener = clientState.mDelegatingListener;
+                run(service -> service.cancel(delegatingListener, isShutdown));
             }
-            clientState.mRecordingInProgress = false;
-
-            // Temporary reference to allow for resetting the hard link mDelegatingListener to null.
-            final IRecognitionListener delegatingListener = clientState.mDelegatingListener;
-            run(service -> service.cancel(delegatingListener, isShutdown));
 
             // If shutdown, remove the client info from the map. Unbind if that was the last client.
             if (isShutdown) {
                 removeClient(listener);
-
                 if (mClients.isEmpty()) {
                     if (DEBUG) {
                         Slog.d(TAG, "Unbinding from the recognition service.");
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index a512331..b271a03 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -196,10 +196,10 @@
     void hideToast(String packageName, IBinder token);
 
     /**
-     * @see com.android.internal.statusbar.IStatusBar#requestWindowMagnificationConnection(boolean
+     * @see com.android.internal.statusbar.IStatusBar#requestMagnificationConnection(boolean
      * request)
      */
-    boolean requestWindowMagnificationConnection(boolean request);
+    boolean requestMagnificationConnection(boolean request);
 
     /**
      * @see com.android.internal.statusbar.IStatusBar#setNavigationBarLumaSamplingEnabled(int,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7c51e7b..b21721a 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -760,11 +760,11 @@
         }
 
         @Override
-        public boolean requestWindowMagnificationConnection(boolean request) {
+        public boolean requestMagnificationConnection(boolean request) {
             IStatusBar bar = mBar;
             if (bar != null) {
                 try {
-                    bar.requestWindowMagnificationConnection(request);
+                    bar.requestMagnificationConnection(request);
                     return true;
                 } catch (RemoteException ex) { }
             }
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
new file mode 100644
index 0000000..2eeb903
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.frameworks.vibrator.IVibratorControlService;
+import android.frameworks.vibrator.IVibratorController;
+import android.frameworks.vibrator.VibrationParam;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.Objects;
+
+/**
+ * Implementation of {@link IVibratorControlService} which allows the registration of
+ * {@link IVibratorController} to set and receive vibration params.
+ *
+ * @hide
+ */
+public final class VibratorControlService extends IVibratorControlService.Stub {
+    private static final String TAG = "VibratorControlService";
+
+    private final VibratorControllerHolder mVibratorControllerHolder;
+    private final Object mLock;
+
+    public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) {
+        mVibratorControllerHolder = vibratorControllerHolder;
+        mLock = lock;
+    }
+
+    @Override
+    public void registerVibratorController(IVibratorController controller)
+            throws RemoteException {
+        synchronized (mLock) {
+            mVibratorControllerHolder.setVibratorController(controller);
+        }
+    }
+
+    @Override
+    public void unregisterVibratorController(@NonNull IVibratorController controller)
+            throws RemoteException {
+        Objects.requireNonNull(controller);
+
+        synchronized (mLock) {
+            if (mVibratorControllerHolder.getVibratorController() == null) {
+                Slog.w(TAG, "Received request to unregister IVibratorController = "
+                        + controller + ", but no controller was previously registered. Request "
+                        + "Ignored.");
+                return;
+            }
+            if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
+                    controller.asBinder())) {
+                Slog.wtf(TAG, "Failed to unregister IVibratorController. The provided "
+                        + "controller doesn't match the registered one. " + this);
+                return;
+            }
+            mVibratorControllerHolder.setVibratorController(null);
+        }
+    }
+
+    @Override
+    public void setVibrationParams(
+            @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token)
+            throws RemoteException {
+        // TODO(b/305939964): Add set vibration implementation.
+    }
+
+    @Override
+    public void clearVibrationParams(int types, IVibratorController token) throws RemoteException {
+        // TODO(b/305939964): Add clear vibration implementation.
+    }
+
+    @Override
+    public void onRequestVibrationParamsComplete(
+            IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
+            throws RemoteException {
+        // TODO(305942827): Cache the vibration params in VibrationScaler
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        return this.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        return this.HASH;
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
new file mode 100644
index 0000000..63e69db
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.frameworks.vibrator.IVibratorController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Holder class for {@link IVibratorController}.
+ *
+ * @hide
+ */
+public final class VibratorControllerHolder implements IBinder.DeathRecipient {
+    private static final String TAG = "VibratorControllerHolder";
+
+    private IVibratorController mVibratorController;
+
+    public IVibratorController getVibratorController() {
+        return mVibratorController;
+    }
+
+    /**
+     * Sets the {@link IVibratorController} in {@link VibratorControllerHolder} to the new
+     * controller. This will also take care of registering and unregistering death notifications
+     * for the cached {@link IVibratorController}.
+     */
+    public void setVibratorController(IVibratorController controller) {
+        try {
+            if (mVibratorController != null) {
+                mVibratorController.asBinder().unlinkToDeath(this, 0);
+            }
+            mVibratorController = controller;
+            if (mVibratorController != null) {
+                mVibratorController.asBinder().linkToDeath(this, 0);
+            }
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to set IVibratorController: " + this, e);
+        }
+    }
+
+    @Override
+    public void binderDied(@NonNull IBinder deadBinder) {
+        if (deadBinder == mVibratorController.asBinder()) {
+            setVibratorController(null);
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        // Should not be used as binderDied(IBinder who) is overridden.
+        Slog.wtf(TAG, "binderDied() called unexpectedly.");
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index cf33cc5..d5044d9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -53,6 +53,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.os.vibrator.VibratorInfoFactory;
@@ -87,10 +88,13 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 
+
 /** System implementation of {@link IVibratorManagerService}. */
 public class VibratorManagerService extends IVibratorManagerService.Stub {
     private static final String TAG = "VibratorManagerService";
     private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
+    private static final String VIBRATOR_CONTROL_SERVICE =
+            "android.frameworks.vibrator.IVibratorControlService/default";
     private static final boolean DEBUG = false;
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
@@ -269,6 +273,10 @@
         context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
 
         injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
+        if (Flags.adaptiveHapticsEnabled()) {
+            injector.addService(VIBRATOR_CONTROL_SERVICE,
+                    new VibratorControlService(new VibratorControllerHolder(), mLock));
+        }
     }
 
     /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 7af4aad..a888f84 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -692,6 +692,7 @@
     void overridePointerIconLocked(int touchSource) {
         mTouchSource = touchSource;
         if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+            // TODO(b/293587049): Pointer Icon Refactor: Set the pointer icon from the drag window.
             InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
         }
     }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index f1cddc6..6f65965 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -26,6 +26,7 @@
 // Log debug messages about InputDispatcherPolicy
 #define DEBUG_INPUT_DISPATCHER_POLICY 0
 
+#include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 #include <android/os/IInputConstants.h>
@@ -308,6 +309,9 @@
     void reloadPointerIcons();
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
     void setCustomPointerIcon(const SpriteIcon& icon);
+    bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+                        int32_t displayId, DeviceId deviceId, int32_t pointerId,
+                        const sp<IBinder>& inputToken);
     void setMotionClassifierEnabled(bool enabled);
     std::optional<std::string> getBluetoothAddress(int32_t deviceId);
     void setStylusButtonMotionEventsEnabled(bool enabled);
@@ -1347,6 +1351,20 @@
     }
 }
 
+bool NativeInputManager::setPointerIcon(
+        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
+        DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken) {
+    if (!mInputManager->getDispatcher().isPointerInWindow(inputToken, displayId, deviceId,
+                                                          pointerId)) {
+        LOG(WARNING) << "Attempted to change the pointer icon for deviceId " << deviceId
+                     << " on display " << displayId << " from input token " << inputToken.get()
+                     << ", but the pointer is not in the window.";
+        return false;
+    }
+
+    return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId);
+}
+
 TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
         JNIEnv *env, jfloatArray matrixArr) {
     ATRACE_CALL();
@@ -2511,6 +2529,32 @@
     im->setCustomPointerIcon(spriteIcon);
 }
 
+static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj,
+                                 jint displayId, jint deviceId, jint pointerId,
+                                 jobject inputTokenObj) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    PointerIcon pointerIcon;
+    status_t result = android_view_PointerIcon_getLoadedIcon(env, iconObj, &pointerIcon);
+    if (result) {
+        jniThrowRuntimeException(env, "Failed to load pointer icon.");
+        return false;
+    }
+
+    std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon;
+    if (pointerIcon.style == PointerIconStyle::TYPE_CUSTOM) {
+        icon = std::make_unique<SpriteIcon>(pointerIcon.bitmap.copy(
+                                                    ANDROID_BITMAP_FORMAT_RGBA_8888),
+                                            pointerIcon.style, pointerIcon.hotSpotX,
+                                            pointerIcon.hotSpotY);
+    } else {
+        icon = pointerIcon.style;
+    }
+
+    return im->setPointerIcon(std::move(icon), displayId, deviceId, pointerId,
+                              ibinderForJavaObject(env, inputTokenObj));
+}
+
 static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                            jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2769,6 +2813,8 @@
         {"reloadPointerIcons", "()V", (void*)nativeReloadPointerIcons},
         {"setCustomPointerIcon", "(Landroid/view/PointerIcon;)V",
          (void*)nativeSetCustomPointerIcon},
+        {"setPointerIcon", "(Landroid/view/PointerIcon;IIILandroid/os/IBinder;)Z",
+         (void*)nativeSetPointerIcon},
         {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
         {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
         {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 627461a..4a2e1cb 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -65,6 +65,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
 import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ApiStatus;
 import com.android.server.infra.AbstractMasterSystemService;
@@ -101,6 +102,13 @@
     private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
             "enable_credential_description_api";
 
+    /**
+     * Value stored in autofill pref when credential provider is primary. This is
+     * used as a placeholder since a credman only provider will not have an
+     * autofill service.
+     */
+    public static final String AUTOFILL_PLACEHOLDER_VALUE = "credential-provider";
+
     private final Context mContext;
 
     /** Cache of system service list per user id. */
@@ -194,6 +202,8 @@
     @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same
     // this.mLock
     protected void handlePackageRemovedMultiModeLocked(String packageName, int userId) {
+        updateProvidersWhenPackageRemoved(mContext, packageName);
+
         List<CredentialManagerServiceImpl> services = peekServiceListForUserLocked(userId);
         if (services == null) {
             return;
@@ -216,8 +226,6 @@
         for (CredentialManagerServiceImpl serviceToBeRemoved : servicesToBeRemoved) {
             removeServiceFromCache(serviceToBeRemoved, userId);
             removeServiceFromSystemServicesCache(serviceToBeRemoved, userId);
-            removeServiceFromMultiModeSettings(serviceToBeRemoved.getComponentName()
-                    .flattenToString(), userId);
             CredentialDescriptionRegistry.forUser(userId)
                     .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
         }
@@ -1114,4 +1122,101 @@
             mRequestSessions.get(userId).put(token, requestSession);
         }
     }
+
+    /** Updates the list of providers when an app is uninstalled. */
+    public static void updateProvidersWhenPackageRemoved(Context context, String packageName) {
+        // Get the current providers.
+        String rawProviders =
+                Settings.Secure.getStringForUser(
+                    context.getContentResolver(),
+                    Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+                    UserHandle.myUserId());
+        if (rawProviders == null) {
+            Slog.w(TAG, "settings key is null");
+            return;
+        }
+
+        // Remove any providers from the primary setting that contain the package name
+        // being removed.
+        Set<String> primaryProviders =
+                getStoredProviders(rawProviders, packageName);
+        if (!Settings.Secure.putString(
+                context.getContentResolver(),
+                Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+                String.join(":", primaryProviders))) {
+            Slog.w(TAG, "Failed to remove primary package: " + packageName);
+            return;
+        }
+
+        // Read the autofill provider so we don't accidentally erase it.
+        String autofillProvider =
+                Settings.Secure.getStringForUser(
+                    context.getContentResolver(),
+                    Settings.Secure.AUTOFILL_SERVICE,
+                    UserHandle.myUserId());
+
+        // If there is an autofill provider and it is the placeholder indicating
+        // that the currently selected primary provider does not support autofill
+        // then we should wipe the setting to keep it in sync.
+        if (autofillProvider != null && primaryProviders.isEmpty()) {
+            if (autofillProvider.equals(AUTOFILL_PLACEHOLDER_VALUE)) {
+                if (!Settings.Secure.putString(
+                        context.getContentResolver(),
+                        Settings.Secure.AUTOFILL_SERVICE,
+                        "")) {
+                    Slog.w(TAG, "Failed to remove autofill package: " + packageName);
+                }
+            } else {
+                // If the existing autofill provider is from the app being removed
+                // then erase the autofill service setting.
+                ComponentName cn = ComponentName.unflattenFromString(autofillProvider);
+                if (cn != null && cn.getPackageName().equals(packageName)) {
+                   if (!Settings.Secure.putString(
+                            context.getContentResolver(),
+                            Settings.Secure.AUTOFILL_SERVICE,
+                            "")) {
+                        Slog.w(TAG, "Failed to remove autofill package: " + packageName);
+                    }
+                }
+            }
+        }
+
+        // Read the credential providers to remove any reference of the removed app.
+        String rawCredentialProviders =
+                Settings.Secure.getStringForUser(
+                    context.getContentResolver(),
+                    Settings.Secure.CREDENTIAL_SERVICE,
+                    UserHandle.myUserId());
+
+        // Remove any providers that belong to the removed app.
+        Set<String> credentialProviders =
+                getStoredProviders(rawCredentialProviders, packageName);
+        if (!Settings.Secure.putString(
+                context.getContentResolver(),
+                Settings.Secure.CREDENTIAL_SERVICE,
+                String.join(":", credentialProviders))) {
+            Slog.w(TAG, "Failed to remove secondary package: " + packageName);
+        }
+    }
+
+    /** Gets the list of stored providers from a string removing any mention of package name. */
+    public static Set<String> getStoredProviders(String rawProviders, String packageName) {
+        // If the app being removed matches any of the package names from
+        // this list then don't add it in the output.
+        Set<String> providers = new HashSet<>();
+        for (String rawComponentName : rawProviders.split(":")) {
+            if (TextUtils.isEmpty(rawComponentName)
+                    || rawComponentName.equals("null")) {
+                Slog.d(TAG, "provider component name is empty or null");
+                continue;
+            }
+
+            ComponentName cn = ComponentName.unflattenFromString(rawComponentName);
+            if (cn != null && !cn.getPackageName().equals(packageName)) {
+                providers.add(cn.flattenToString());
+            }
+        }
+
+        return providers;
+    }
 }
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 7638915..e2fdfe9 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,4 +4,14 @@
         <version>1</version>
         <fqname>IAltitudeService/default</fqname>
     </hal>
+    <hal format="aidl">
+        <name>android.frameworks.vibrator</name>
+        <version>1</version>
+        <fqname>IVibratorController/default</fqname>
+    </hal>
+    <hal format="aidl">
+        <name>android.frameworks.vibrator</name>
+        <version>1</version>
+        <fqname>IVibratorControlService/default</fqname>
+    </hal>
 </manifest>
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 693cafe..acd9dce 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -727,6 +727,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
     public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
         Settings.System.putInt(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -762,6 +763,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
     public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
         Settings.System.putInt(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index b227993..50b0e16 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1202,6 +1202,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
     public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
         Settings.System.putInt(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -1236,6 +1237,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FAST_HDR_TRANSITIONS)
     public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
         Settings.System.putInt(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index 72dc725..4ba9d60 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -17,7 +17,6 @@
 package com.android.server.am;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -44,6 +43,9 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.DropBoxManagerInternal;
 import com.android.server.LocalServices;
@@ -85,6 +87,12 @@
     public final ApplicationExitInfoTest.ServiceThreadRule
             mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
 
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .spyStatic(FrameworkStatsLog.class)
+            .spyStatic(ProcessList.class)
+            .build();
+
     @Mock
     AppOpsService mAppOpsService;
     @Mock
@@ -140,6 +148,7 @@
         realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
         realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
         realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
+        ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
         realAms.mPackageManagerInt = mPackageManagerInt;
         realAms.mUsageStatsService = mUsageStatsManagerInt;
         realAms.mProcessesReady = true;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 2378416..c03799d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -79,11 +79,9 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.modules.utils.testing.ExtendedMockitoRule;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
 
@@ -112,11 +110,6 @@
 
     BroadcastProcessQueue mHead;
 
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
-            .spyStatic(FrameworkStatsLog.class)
-            .build();
-
     @Before
     public void setUp() throws Exception {
         super.setUp();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index a3ec936..3e73aa3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -228,7 +228,7 @@
         Exception e = assertThrows(
                 SecurityException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, "different", mIntentSender,
-                        UserHandle.CURRENT));
+                        UserHandle.CURRENT, 0));
         assertThat(e).hasMessageThat().isEqualTo(
                 String.format(
                         "The UID %s of callerPackageName set by the caller doesn't match the "
@@ -245,7 +245,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT));
+                        UserHandle.CURRENT, 0));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s not found.", PACKAGE));
@@ -255,7 +255,8 @@
     public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException {
         mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
 
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
+                0);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -285,7 +286,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT));
+                        UserHandle.CURRENT, 0));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo("No installer found");
     }
@@ -299,7 +300,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT));
+                        UserHandle.CURRENT, 0));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 "Installer does not support unarchival");
@@ -313,7 +314,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT));
+                        UserHandle.CURRENT, 0));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
@@ -325,7 +326,8 @@
         doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
                 any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
 
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
+                0);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -348,15 +350,16 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT));
+                        UserHandle.CURRENT, 0));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 TextUtils.formatSimple("The app %s is opted out of archiving.", PACKAGE));
     }
 
     @Test
-    public void archiveApp_success() {
-        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+    public void archiveApp_withNoAdditionalFlags_success() {
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
+                0);
         rule.mocks().getHandler().flush();
 
         verify(mInstallerService).uninstall(
@@ -369,6 +372,23 @@
     }
 
     @Test
+    public void archiveApp_withAdditionalFlags_success() {
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
+                PackageManager.DELETE_SHOW_DIALOG);
+        rule.mocks().getHandler().flush();
+
+        verify(mInstallerService).uninstall(
+                eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
+                eq(CALLER_PACKAGE),
+                eq(DELETE_ARCHIVE | DELETE_KEEP_DATA | PackageManager.DELETE_SHOW_DIALOG),
+                eq(mIntentSender),
+                eq(UserHandle.CURRENT.getIdentifier()), anyInt());
+        assertThat(mPackageSetting.readUserState(
+                UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
+                createArchiveState());
+    }
+
+    @Test
     public void isAppArchivable_success() throws PackageManager.NameNotFoundException {
         assertThat(mArchiveManager.isAppArchivable(PACKAGE, UserHandle.CURRENT)).isTrue();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 305569e..fd6aa0c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -27,6 +27,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -47,10 +48,12 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.util.Xml;
 
 import androidx.test.annotation.UiThreadTest;
 
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.am.UserState;
@@ -62,8 +65,12 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 
 /**
  * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest}
@@ -96,6 +103,12 @@
      */
     private static final int PROFILE_USER_ID = 643;
 
+    private static final String USER_INFO_DIR = "system" + File.separator + "users";
+
+    private static final String XML_SUFFIX = ".xml";
+
+    private static final String TAG_RESTRICTIONS = "restrictions";
+
     @Rule
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
             .spyStatic(UserManager.class)
@@ -530,6 +543,48 @@
         assertThat(user1.name.length()).isEqualTo(4);
     }
 
+    @Test
+    public void testDefaultRestrictionsArePersistedAfterCreateUser()
+            throws IOException, XmlPullParserException {
+        UserInfo user = mUms.createUserWithThrow("Test", USER_TYPE_FULL_SECONDARY, 0);
+        assertTrue(hasRestrictionsInUserXMLFile(user.id));
+    }
+
+    /**
+     * Returns true if the user's XML file has Default restrictions
+     * @param userId Id of the user.
+     */
+    private boolean hasRestrictionsInUserXMLFile(int userId)
+            throws IOException, XmlPullParserException {
+        FileInputStream is = new FileInputStream(getUserXmlFile(userId));
+        final TypedXmlPullParser parser = Xml.resolvePullParser(is);
+
+        int type;
+        while ((type = parser.next()) != XmlPullParser.START_TAG
+                && type != XmlPullParser.END_DOCUMENT) {
+            // Skip
+        }
+
+        if (type != XmlPullParser.START_TAG) {
+            return false;
+        }
+
+        int outerDepth = parser.getDepth();
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (TAG_RESTRICTIONS.equals(parser.getName())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private File getUserXmlFile(int userId) {
+        File file = new File(mTestDir, USER_INFO_DIR);
+        return new File(file, userId + XML_SUFFIX);
+    }
+
     private String generateLongString() {
         String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
                 + "Name Test Name Test Name Test Name "; //String of length 100
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 18a4f00..f02e5a5 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -11,6 +11,10 @@
         "src/**/*.java",
     ],
 
+    exclude_srcs: [
+        "src/com/android/server/power/stats/PowerStatsStoreTest.java",
+    ],
+
     static_libs: [
         "services.core",
         "coretests-aidl",
@@ -52,3 +56,19 @@
         enabled: false,
     },
 }
+
+android_ravenwood_test {
+    name: "PowerStatsTestsRavenwood",
+    static_libs: [
+        "services.core",
+        "modules-utils-binary-xml",
+
+        "androidx.annotation_annotation",
+        "androidx.test.rules",
+    ],
+    srcs: [
+        "src/com/android/server/power/stats/PowerStatsStoreTest.java",
+    ],
+    sdk_version: "test_current",
+    auto_gen_config: true,
+}
diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING
index eee68a4..6d3db1c 100644
--- a/services/tests/powerstatstests/TEST_MAPPING
+++ b/services/tests/powerstatstests/TEST_MAPPING
@@ -9,6 +9,12 @@
       ]
     }
   ],
+  "ravenwood-presubmit": [
+    {
+      "name": "PowerStatsTestsRavenwood",
+      "host": true
+    }
+  ],
   "postsubmit": [
     {
       "name": "PowerStatsTests",
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
index d3628b5..36d7af5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java
@@ -18,18 +18,18 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.platform.test.ravenwood.RavenwoodRule;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
@@ -37,6 +37,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
@@ -44,14 +45,17 @@
 public class PowerStatsStoreTest {
     private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024;
 
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private PowerStatsStore mPowerStatsStore;
     private File mStoreDirectory;
 
     @Before
-    public void setup() {
-        Context context = InstrumentationRegistry.getContext();
-
-        mStoreDirectory = new File(context.getCacheDir(), "PowerStatsStoreTest");
+    public void setup() throws IOException {
+        mStoreDirectory = Files.createTempDirectory("PowerStatsStoreTest").toFile();
         clearDirectory(mStoreDirectory);
 
         mPowerStatsStore = new PowerStatsStore(mStoreDirectory,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 1b02498..52726ca 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -19,7 +19,7 @@
 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
 
 import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
-import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY;
+import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
index 3843e25..a7cf361 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.accessibility.magnification;
 
-import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY;
-import static com.android.server.accessibility.magnification.MockWindowMagnificationConnection.TEST_DISPLAY_2;
+import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
+import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY_2;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -79,7 +79,7 @@
     private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
     private static final int SERVICE_ID = 1;
 
-    private MockWindowMagnificationConnection mMockConnection;
+    private MockMagnificationConnection mMockConnection;
     @Mock
     private Context mContext;
     @Mock
@@ -99,7 +99,7 @@
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
         LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal);
         mResolver = new MockContentResolver();
-        mMockConnection = new MockWindowMagnificationConnection();
+        mMockConnection = new MockMagnificationConnection();
         mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, new Object(),
                 mMockCallback, mMockTrace, new MagnificationScaleProvider(mContext));
 
@@ -128,7 +128,7 @@
                         connect ? mMockConnection.getConnection() : null);
             }
             return true;
-        }).when(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(anyBoolean());
+        }).when(mMockStatusBarManagerInternal).requestMagnificationConnection(anyBoolean());
     }
 
     @Test
@@ -169,8 +169,7 @@
     public void setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath()
             throws RemoteException {
         mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
-        MockWindowMagnificationConnection secondConnection =
-                new MockWindowMagnificationConnection();
+        MockMagnificationConnection secondConnection = new MockMagnificationConnection();
 
         mMagnificationConnectionManager.setConnection(secondConnection.getConnection());
         mMockConnection.getDeathRecipient().binderDied();
@@ -620,13 +619,13 @@
         assertTrue(mMagnificationConnectionManager.requestConnection(false));
 
         verify(mMockConnection.getConnection()).disableWindowMagnification(TEST_DISPLAY, null);
-        verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(false);
+        verify(mMockStatusBarManagerInternal).requestMagnificationConnection(false);
     }
 
     @Test
     public void requestConnection_requestWindowMagnificationConnection() throws RemoteException {
         assertTrue(mMagnificationConnectionManager.requestConnection(true));
-        verify(mMockStatusBarManagerInternal).requestWindowMagnificationConnection(true);
+        verify(mMockStatusBarManagerInternal).requestMagnificationConnection(true);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
index 8f85f11..8fdd884 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
@@ -24,8 +24,8 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.view.Display;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 import android.view.accessibility.MagnificationAnimationCallback;
 
@@ -45,7 +45,7 @@
 
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
 
-    private IWindowMagnificationConnection mConnection;
+    private IMagnificationConnection mConnection;
     @Mock
     private AccessibilityTraceManager mTrace;
     @Mock
@@ -53,14 +53,14 @@
     @Mock
     private MagnificationAnimationCallback mAnimationCallback;
 
-    private MockWindowMagnificationConnection mMockWindowMagnificationConnection;
+    private MockMagnificationConnection mMockMagnificationConnection;
     private MagnificationConnectionWrapper mConnectionWrapper;
 
     @Before
     public void setUp() throws RemoteException {
         MockitoAnnotations.initMocks(this);
-        mMockWindowMagnificationConnection = new MockWindowMagnificationConnection();
-        mConnection = mMockWindowMagnificationConnection.getConnection();
+        mMockMagnificationConnection = new MockMagnificationConnection();
+        mConnection = mMockMagnificationConnection.getConnection();
         mConnectionWrapper = new MagnificationConnectionWrapper(mConnection, mTrace);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index e8cdf35..28d07f9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -130,7 +130,7 @@
     @Captor
     private ArgumentCaptor<MagnificationAnimationCallback> mCallbackArgumentCaptor;
 
-    private MockWindowMagnificationConnection mMockConnection;
+    private MockMagnificationConnection mMockConnection;
     private MagnificationConnectionManager mMagnificationConnectionManager;
     private MockContentResolver mMockResolver;
     private MagnificationController mMagnificationController;
@@ -208,7 +208,7 @@
         mMagnificationConnectionManager = spy(
                 new MagnificationConnectionManager(mContext, globalLock,
                         mWindowMagnificationCallbackDelegate, mTraceManager, mScaleProvider));
-        mMockConnection = new MockWindowMagnificationConnection(true);
+        mMockConnection = new MockMagnificationConnection(true);
         mMagnificationConnectionManager.setConnection(mMockConnection.getConnection());
 
         mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java
rename to services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java
index 4c03ec3..3d3d0b7 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockMagnificationConnection.java
@@ -31,8 +31,8 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.view.Display;
+import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.view.accessibility.IWindowMagnificationConnection;
 import android.view.accessibility.IWindowMagnificationConnectionCallback;
 
 import java.util.ArrayList;
@@ -42,12 +42,12 @@
  * Mocks the basic logic of window magnification in System UI. We assume the screen size is
  * unlimited, so source bounds is always on the center of the mirror window bounds.
  */
-class MockWindowMagnificationConnection {
+class MockMagnificationConnection {
 
     public static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     public static final int TEST_DISPLAY_2 = Display.DEFAULT_DISPLAY + 1;
     private final List mValidDisplayIds;
-    private final IWindowMagnificationConnection mConnection;
+    private final IMagnificationConnection mConnection;
     private final Binder mBinder;
     private final boolean mSuspendCallback;
     private boolean mHasPendingCallback = false;
@@ -60,17 +60,17 @@
     private Rect mSourceBounds = new Rect();
     private IRemoteMagnificationAnimationCallback mAnimationCallback;
 
-    MockWindowMagnificationConnection() throws RemoteException {
+    MockMagnificationConnection() throws RemoteException {
         this(false);
     }
 
-    MockWindowMagnificationConnection(boolean suspendCallback) throws RemoteException {
+    MockMagnificationConnection(boolean suspendCallback) throws RemoteException {
         mValidDisplayIds = new ArrayList();
         mValidDisplayIds.add(TEST_DISPLAY);
         mValidDisplayIds.add(TEST_DISPLAY_2);
 
         mSuspendCallback = suspendCallback;
-        mConnection = mock(IWindowMagnificationConnection.class);
+        mConnection = mock(IMagnificationConnection.class);
         mBinder = mock(Binder.class);
         when(mConnection.asBinder()).thenReturn(mBinder);
         doAnswer((invocation) -> {
@@ -154,7 +154,7 @@
         }
     }
 
-    IWindowMagnificationConnection getConnection() {
+    IMagnificationConnection getConnection() {
         return mConnection;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index c4be51f..a3b67ae 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -86,14 +86,14 @@
     public static final float DEFAULT_TAP_X = 301;
     public static final float DEFAULT_TAP_Y = 299;
     public static final PointF DEFAULT_POINT = new PointF(DEFAULT_TAP_X, DEFAULT_TAP_Y);
-    private static final int DISPLAY_0 = MockWindowMagnificationConnection.TEST_DISPLAY;
+    private static final int DISPLAY_0 = MockMagnificationConnection.TEST_DISPLAY;
 
     @Rule
     public final TestableContext mContext = new TestableContext(
             InstrumentationRegistry.getInstrumentation().getContext());
 
     private MagnificationConnectionManager mMagnificationConnectionManager;
-    private MockWindowMagnificationConnection mMockConnection;
+    private MockMagnificationConnection mMockConnection;
     private SpyWindowMagnificationGestureHandler mWindowMagnificationGestureHandler;
     private WindowMagnificationGestureHandler mMockWindowMagnificationGestureHandler;
     @Mock
@@ -107,7 +107,7 @@
         mMagnificationConnectionManager = new MagnificationConnectionManager(mContext, new Object(),
                 mock(MagnificationConnectionManager.Callback.class), mMockTrace,
                 new MagnificationScaleProvider(mContext));
-        mMockConnection = new MockWindowMagnificationConnection();
+        mMockConnection = new MockMagnificationConnection();
         mWindowMagnificationGestureHandler = new SpyWindowMagnificationGestureHandler(
                 mContext, mMagnificationConnectionManager, mMockTrace, mMockCallback,
                 /** detectSingleFingerTripleTap= */ true, /** detectTwoFingerTripleTap= */ true,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index b732d38..3355910 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -26,10 +26,12 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -70,6 +72,7 @@
 @RunWith(AndroidJUnit4.class)
 public class GenericWindowPolicyControllerTest {
 
+    private static final int TIMEOUT_MILLIS = 500;
     private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
     private static final int TEST_UID = 1234567;
     private static final String DISPLAY_CATEGORY = "com.display.category";
@@ -134,7 +137,7 @@
         GenericWindowPolicyController gwpc = createGwpc();
 
         assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isFalse();
-        verify(mPipBlockedCallback).onEnteringPipBlocked(TEST_UID);
+        verify(mPipBlockedCallback, timeout(TIMEOUT_MILLIS)).onEnteringPipBlocked(TEST_UID);
     }
 
     @Test
@@ -144,7 +147,7 @@
                 Arrays.asList(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
                         WindowConfiguration.WINDOWING_MODE_PINNED)));
         assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isTrue();
-        verify(mPipBlockedCallback, never()).onEnteringPipBlocked(TEST_UID);
+        verify(mPipBlockedCallback, after(TIMEOUT_MILLIS).never()).onEnteringPipBlocked(TEST_UID);
     }
 
     @Test
@@ -496,7 +499,7 @@
         gwpc.onRunningAppsChanged(uids);
 
         assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(1);
-        verify(mRunningAppsChangedListener).onRunningAppsChanged(uids);
+        verify(mRunningAppsChangedListener, timeout(TIMEOUT_MILLIS)).onRunningAppsChanged(uids);
     }
 
     @Test
@@ -508,7 +511,7 @@
         gwpc.onRunningAppsChanged(uids);
 
         assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
-        verify(mActivityListener).onDisplayEmpty(DISPLAY_ID);
+        verify(mActivityListener, timeout(TIMEOUT_MILLIS)).onDisplayEmpty(DISPLAY_ID);
     }
 
     @Test
@@ -519,7 +522,8 @@
         gwpc.onRunningAppsChanged(uids);
 
         assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
-        verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids);
+        verify(mRunningAppsChangedListener, after(TIMEOUT_MILLIS).never())
+                .onRunningAppsChanged(uids);
     }
 
     @Test
@@ -532,7 +536,8 @@
         gwpc.onRunningAppsChanged(uids);
 
         assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
-        verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids);
+        verify(mRunningAppsChangedListener, after(TIMEOUT_MILLIS).never())
+                .onRunningAppsChanged(uids);
     }
 
     @Test
@@ -582,7 +587,8 @@
         assertThat(gwpc.canActivityBeLaunched(activityInfo, intent,
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
                 .isTrue();
-        verify(mIntentListenerCallback).shouldInterceptIntent(any(Intent.class));
+        verify(mIntentListenerCallback, timeout(TIMEOUT_MILLIS))
+                .shouldInterceptIntent(any(Intent.class));
     }
 
     @Test
@@ -590,7 +596,7 @@
         GenericWindowPolicyController gwpc = createGwpc();
 
         gwpc.onTopActivityChanged(null, 0, 0);
-        verify(mActivityListener, never())
+        verify(mActivityListener, after(TIMEOUT_MILLIS).never())
                 .onTopActivityChanged(anyInt(), any(ComponentName.class), anyInt());
     }
 
@@ -601,7 +607,7 @@
         gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
 
         gwpc.onTopActivityChanged(BLOCKED_COMPONENT, 0, userId);
-        verify(mActivityListener)
+        verify(mActivityListener, timeout(TIMEOUT_MILLIS))
                 .onTopActivityChanged(eq(DISPLAY_ID), eq(BLOCKED_COMPONENT), eq(userId));
     }
 
@@ -618,8 +624,8 @@
 
         assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue();
 
-        verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID,
-                activityInfo.applicationInfo.uid);
+        verify(mSecureWindowCallback, after(TIMEOUT_MILLIS).never())
+                .onSecureWindowShown(DISPLAY_ID, activityInfo.applicationInfo.uid);
         verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
     }
 
@@ -636,9 +642,10 @@
 
         assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, FLAG_SECURE, 0)).isTrue();
 
-        verify(mSecureWindowCallback).onSecureWindowShown(DISPLAY_ID,
+        verify(mSecureWindowCallback, timeout(TIMEOUT_MILLIS)).onSecureWindowShown(DISPLAY_ID,
                 activityInfo.applicationInfo.uid);
-        verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
+        verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never())
+                .onActivityBlocked(DISPLAY_ID, activityInfo);
     }
 
     @Test
@@ -655,8 +662,8 @@
         assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0,
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)).isTrue();
 
-        verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID,
-                activityInfo.applicationInfo.uid);
+        verify(mSecureWindowCallback, after(TIMEOUT_MILLIS).never())
+                .onSecureWindowShown(DISPLAY_ID, activityInfo.applicationInfo.uid);
         verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo);
     }
 
@@ -882,7 +889,8 @@
         assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
                 isNewTask)).isTrue();
 
-        verify(mActivityBlockedCallback, never()).onActivityBlocked(fromDisplay, activityInfo);
+        verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never())
+                .onActivityBlocked(fromDisplay, activityInfo);
         verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
     }
 
@@ -897,8 +905,10 @@
         assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay,
                 isNewTask)).isFalse();
 
-        verify(mActivityBlockedCallback).onActivityBlocked(fromDisplay, activityInfo);
-        verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
+        verify(mActivityBlockedCallback, timeout(TIMEOUT_MILLIS))
+                .onActivityBlocked(fromDisplay, activityInfo);
+        verify(mIntentListenerCallback, after(TIMEOUT_MILLIS).never())
+                .shouldInterceptIntent(any(Intent.class));
     }
 
     private void assertNoActivityLaunched(GenericWindowPolicyController gwpc, int fromDisplay,
@@ -907,7 +917,8 @@
                 WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, true))
                 .isFalse();
 
-        verify(mActivityBlockedCallback, never()).onActivityBlocked(fromDisplay, activityInfo);
+        verify(mActivityBlockedCallback, after(TIMEOUT_MILLIS).never())
+                .onActivityBlocked(fromDisplay, activityInfo);
         verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
new file mode 100644
index 0000000..fd1abff
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialManagerServiceTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.credentials;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.cert.CertificateException;
+import java.util.HashSet;
+import java.util.Set;
+
+/** atest FrameworksServicesTests:com.android.server.credentials.CredentialManagerServiceTest */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class CredentialManagerServiceTest {
+
+    Context mContext = null;
+
+    @Before
+    public void setUp() throws CertificateException {
+        mContext = ApplicationProvider.getApplicationContext();
+    }
+
+    @Test
+    public void getStoredProviders_emptyValue_success() {
+        Set<String> providers = CredentialManagerService.getStoredProviders("", "");
+        assertThat(providers.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void getStoredProviders_success() {
+        Set<String> providers =
+                CredentialManagerService.getStoredProviders(
+                        "com.example.test/.TestActivity:com.example.test/.TestActivity2:"
+                                + "com.example.test2/.TestActivity:blank",
+                        "com.example.test");
+        assertThat(providers.size()).isEqualTo(1);
+        assertThat(providers.contains("com.example.test2/com.example.test2.TestActivity")).isTrue();
+    }
+
+    @Test
+    public void onProviderRemoved_success() {
+        setSettingsKey(
+                Settings.Secure.AUTOFILL_SERVICE,
+                CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE);
+        setSettingsKey(
+                Settings.Secure.CREDENTIAL_SERVICE,
+                "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity");
+        setSettingsKey(
+                Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+                "com.example.test/com.example.test.TestActivity");
+
+        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test");
+
+        assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo("");
+        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE))
+                .isEqualTo("com.example.test2/com.example.test2.TestActivity");
+        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY)).isEqualTo("");
+    }
+
+    @Test
+    public void onProviderRemoved_notPrimaryRemoved_success() {
+        final String testCredentialPrimaryValue = "com.example.test/com.example.test.TestActivity";
+        final String testCredentialValue =
+                "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity";
+
+        setSettingsKey(
+                Settings.Secure.AUTOFILL_SERVICE,
+                CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE);
+        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue);
+        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue);
+
+        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3");
+
+        // Since the provider removed was not a primary provider then we should do nothing.
+        assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE))
+                .isEqualTo(CredentialManagerService.AUTOFILL_PLACEHOLDER_VALUE);
+        assertCredentialPropertyEquals(
+                getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE), testCredentialValue);
+        assertCredentialPropertyEquals(
+                getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY),
+                testCredentialPrimaryValue);
+    }
+
+    @Test
+    public void onProviderRemoved_isAlsoAutofillProvider_success() {
+        setSettingsKey(
+                Settings.Secure.AUTOFILL_SERVICE,
+                "com.example.test/com.example.test.AutofillProvider");
+        setSettingsKey(
+                Settings.Secure.CREDENTIAL_SERVICE,
+                "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity");
+        setSettingsKey(
+                Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+                "com.example.test/com.example.test.TestActivity");
+
+        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test");
+
+        assertThat(getSettingsKey(Settings.Secure.AUTOFILL_SERVICE)).isEqualTo("");
+        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE))
+                .isEqualTo("com.example.test2/com.example.test2.TestActivity");
+        assertThat(getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY)).isEqualTo("");
+    }
+
+    @Test
+    public void onProviderRemoved_notPrimaryRemoved_isAlsoAutofillProvider_success() {
+        final String testCredentialPrimaryValue = "com.example.test/com.example.test.TestActivity";
+        final String testCredentialValue =
+                "com.example.test/com.example.test.TestActivity:com.example.test2/com.example.test2.TestActivity";
+        final String testAutofillValue = "com.example.test/com.example.test.TestAutofillActivity";
+
+        setSettingsKey(Settings.Secure.AUTOFILL_SERVICE, testAutofillValue);
+        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE, testCredentialValue);
+        setSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, testCredentialPrimaryValue);
+
+        CredentialManagerService.updateProvidersWhenPackageRemoved(mContext, "com.example.test3");
+
+        // Since the provider removed was not a primary provider then we should do nothing.
+        assertCredentialPropertyEquals(
+                getSettingsKey(Settings.Secure.AUTOFILL_SERVICE), testAutofillValue);
+        assertCredentialPropertyEquals(
+                getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE), testCredentialValue);
+        assertCredentialPropertyEquals(
+                getSettingsKey(Settings.Secure.CREDENTIAL_SERVICE_PRIMARY),
+                testCredentialPrimaryValue);
+    }
+
+    private void assertCredentialPropertyEquals(String actualValue, String newValue) {
+        Set<ComponentName> actualValueSet = new HashSet<>();
+        for (String rawComponentName : actualValue.split(":")) {
+            ComponentName cn = ComponentName.unflattenFromString(rawComponentName);
+            if (cn != null) {
+                actualValueSet.add(cn);
+            }
+        }
+
+        Set<ComponentName> newValueSet = new HashSet<>();
+        for (String rawComponentName : newValue.split(":")) {
+            ComponentName cn = ComponentName.unflattenFromString(rawComponentName);
+            if (cn != null) {
+                newValueSet.add(cn);
+            }
+        }
+
+        assertThat(actualValueSet).isEqualTo(newValueSet);
+    }
+
+    private void setSettingsKey(String key, String value) {
+        assertThat(Settings.Secure.putString(mContext.getContentResolver(), key, value)).isTrue();
+    }
+
+    private String getSettingsKey(String key) {
+        return Settings.Secure.getStringForUser(
+                mContext.getContentResolver(), key, UserHandle.myUserId());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 4b318de..37a1a41 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -287,7 +287,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_EN_US), imi);
+                            new LocaleList(LOCALE_EN_US), imi);
             assertEquals(1, result.size());
             verifyEquality(autoSubtype, result.get(0));
         }
@@ -311,7 +311,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_EN_US), imi);
+                            new LocaleList(LOCALE_EN_US), imi);
             assertEquals(2, result.size());
             verifyEquality(nonAutoEnUS, result.get(0));
             verifyEquality(nonAutoHandwritingEn, result.get(1));
@@ -335,7 +335,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_EN_GB), imi);
+                            new LocaleList(LOCALE_EN_GB), imi);
             assertEquals(2, result.size());
             verifyEquality(nonAutoEnGB, result.get(0));
             verifyEquality(nonAutoHandwritingEn, result.get(1));
@@ -360,7 +360,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_FR), imi);
+                            new LocaleList(LOCALE_FR), imi);
             assertEquals(2, result.size());
             verifyEquality(nonAutoFrCA, result.get(0));
             verifyEquality(nonAutoHandwritingFr, result.get(1));
@@ -381,7 +381,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_FR_CA), imi);
+                            new LocaleList(LOCALE_FR_CA), imi);
             assertEquals(2, result.size());
             verifyEquality(nonAutoFrCA, result.get(0));
             verifyEquality(nonAutoHandwritingFr, result.get(1));
@@ -403,7 +403,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_JA_JP), imi);
+                            new LocaleList(LOCALE_JA_JP), imi);
             assertEquals(3, result.size());
             verifyEquality(nonAutoJa, result.get(0));
             verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, result.get(1));
@@ -425,7 +425,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_JA_JP), imi);
+                            new LocaleList(LOCALE_JA_JP), imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoHi, result.get(0));
         }
@@ -442,7 +442,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_JA_JP), imi);
+                            new LocaleList(LOCALE_JA_JP), imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoEnUS, result.get(0));
         }
@@ -459,7 +459,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_JA_JP), imi);
+                            new LocaleList(LOCALE_JA_JP), imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoEnUS, result.get(0));
         }
@@ -481,7 +481,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(Locale.forLanguageTag("sr-Latn-RS")), imi);
+                            new LocaleList(Locale.forLanguageTag("sr-Latn-RS")), imi);
             assertEquals(2, result.size());
             assertThat(nonAutoSrLatn, is(in(result)));
             assertThat(nonAutoHandwritingSrLatn, is(in(result)));
@@ -501,7 +501,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(Locale.forLanguageTag("sr-Cyrl-RS")), imi);
+                            new LocaleList(Locale.forLanguageTag("sr-Cyrl-RS")), imi);
             assertEquals(2, result.size());
             assertThat(nonAutoSrCyrl, is(in(result)));
             assertThat(nonAutoHandwritingSrCyrl, is(in(result)));
@@ -527,7 +527,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(
+                            new LocaleList(
                                     Locale.forLanguageTag("sr-Latn-RS-x-android"),
                                     Locale.forLanguageTag("ja-JP"),
                                     Locale.forLanguageTag("fr-FR"),
@@ -554,7 +554,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_FIL_PH), imi);
+                            new LocaleList(LOCALE_FIL_PH), imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoFil, result.get(0));
         }
@@ -572,7 +572,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_FI), imi);
+                            new LocaleList(LOCALE_FI), imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoJa, result.get(0));
         }
@@ -588,7 +588,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_IN), imi);
+                            new LocaleList(LOCALE_IN), imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoIn, result.get(0));
         }
@@ -602,7 +602,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_ID), imi);
+                            new LocaleList(LOCALE_ID), imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoIn, result.get(0));
         }
@@ -616,7 +616,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_IN), imi);
+                            new LocaleList(LOCALE_IN), imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoId, result.get(0));
         }
@@ -630,7 +630,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_ID), imi);
+                            new LocaleList(LOCALE_ID), imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoId, result.get(0));
         }
@@ -652,7 +652,7 @@
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
                     SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
-                            getResourcesForLocales(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi);
+                            new LocaleList(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi);
             assertThat(nonAutoFrCA, is(in(result)));
             assertThat(nonAutoEnUS, is(in(result)));
             assertThat(nonAutoJa, is(in(result)));
@@ -940,10 +940,6 @@
                 .createConfigurationContext(resourceConfiguration);
     }
 
-    private Resources getResourcesForLocales(Locale... locales) {
-        return createTargetContextWithLocales(new LocaleList(locales)).getResources();
-    }
-
     private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) {
         final String[] packageNames = new String[imis.size()];
         for (int i = 0; i < imis.size(); ++i) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 97b6b98..4d25eaa 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -524,7 +524,7 @@
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
 
-        for (int usage : AudioAttributes.SDK_USAGES) {
+        for (int usage : AudioAttributes.getSdkUsages()) {
             if (usage == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) {
                 // only mute audio, not vibrations
                 verify(mAppOps, atLeastOnce()).setRestriction(eq(AppOpsManager.OP_PLAY_AUDIO),
@@ -546,7 +546,7 @@
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
 
-        for (int usage : AudioAttributes.SDK_USAGES) {
+        for (int usage : AudioAttributes.getSdkUsages()) {
             verify(mAppOps).setRestriction(
                     eq(AppOpsManager.OP_PLAY_AUDIO), eq(usage), anyInt(), eq(new String[]{PKG_O}));
             verify(mAppOps).setRestriction(
@@ -561,7 +561,7 @@
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
 
-        for (int usage : AudioAttributes.SDK_USAGES) {
+        for (int usage : AudioAttributes.getSdkUsages()) {
             verify(mAppOps).setRestriction(
                     eq(AppOpsManager.OP_PLAY_AUDIO), eq(usage), anyInt(), eq(null));
             verify(mAppOps).setRestriction(
@@ -576,7 +576,7 @@
         mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
         mZenModeHelper.applyRestrictions();
 
-        for (int usage : AudioAttributes.SDK_USAGES) {
+        for (int usage : AudioAttributes.getSdkUsages()) {
             verify(mAppOps).setRestriction(
                     eq(AppOpsManager.OP_PLAY_AUDIO), eq(usage), anyInt(), eq(null));
             verify(mAppOps).setRestriction(
@@ -1052,6 +1052,88 @@
     }
 
     @Test
+    public void testProtoWithAutoRuleCustomPolicy_classic() throws Exception {
+        setupZenConfig();
+        // clear any automatic rules just to make sure
+        mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
+
+        // Add an automatic rule with a custom policy
+        ZenRule rule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS, CUSTOM_RULE_ID);
+        rule.zenPolicy = new ZenPolicy.Builder()
+                .allowAlarms(true)
+                .allowRepeatCallers(false)
+                .allowCalls(PEOPLE_TYPE_STARRED)
+                .build();
+        mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
+        List<StatsEvent> events = new LinkedList<>();
+        mZenModeHelper.pullRules(events);
+
+        boolean foundCustomEvent = false;
+        for (StatsEvent ev : events) {
+            AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+            assertTrue(atom.hasDndModeRule());
+            DNDModeProto cfg = atom.getDndModeRule();
+            if (cfg.getUid() == CUSTOM_PKG_UID) {
+                foundCustomEvent = true;
+                // Check that the pieces of the policy are applied.
+                assertThat(cfg.hasPolicy()).isTrue();
+                DNDPolicyProto policy = cfg.getPolicy();
+                assertThat(policy.getAlarms().getNumber()).isEqualTo(DNDProtoEnums.STATE_ALLOW);
+                assertThat(policy.getRepeatCallers().getNumber())
+                        .isEqualTo(DNDProtoEnums.STATE_DISALLOW);
+                assertThat(policy.getCalls().getNumber()).isEqualTo(DNDProtoEnums.STATE_ALLOW);
+                assertThat(policy.getAllowCallsFrom().getNumber())
+                        .isEqualTo(DNDProtoEnums.PEOPLE_STARRED);
+            }
+        }
+        assertTrue("couldn't find custom rule", foundCustomEvent);
+    }
+
+    @Test
+    public void testProtoWithAutoRuleCustomPolicy() throws Exception {
+        // allowChannels is only valid under modes_api.
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        setupZenConfig();
+        // clear any automatic rules just to make sure
+        mZenModeHelper.mConfig.automaticRules = new ArrayMap<>();
+
+        // Add an automatic rule with a custom policy
+        ZenRule rule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS, CUSTOM_RULE_ID);
+        rule.zenPolicy = new ZenPolicy.Builder()
+                .allowAlarms(true)
+                .allowRepeatCallers(false)
+                .allowCalls(PEOPLE_TYPE_STARRED)
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_NONE)
+                .build();
+        mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
+        List<StatsEvent> events = new LinkedList<>();
+        mZenModeHelper.pullRules(events);
+
+        boolean foundCustomEvent = false;
+        for (StatsEvent ev : events) {
+            AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
+            assertTrue(atom.hasDndModeRule());
+            DNDModeProto cfg = atom.getDndModeRule();
+            if (cfg.getUid() == CUSTOM_PKG_UID) {
+                foundCustomEvent = true;
+                // Check that the pieces of the policy are applied.
+                assertThat(cfg.hasPolicy()).isTrue();
+                DNDPolicyProto policy = cfg.getPolicy();
+                assertThat(policy.getAlarms().getNumber()).isEqualTo(DNDProtoEnums.STATE_ALLOW);
+                assertThat(policy.getRepeatCallers().getNumber())
+                        .isEqualTo(DNDProtoEnums.STATE_DISALLOW);
+                assertThat(policy.getCalls().getNumber()).isEqualTo(DNDProtoEnums.STATE_ALLOW);
+                assertThat(policy.getAllowCallsFrom().getNumber())
+                        .isEqualTo(DNDProtoEnums.PEOPLE_STARRED);
+                assertThat(policy.getAllowChannels().getNumber())
+                        .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_NONE);
+            }
+        }
+        assertTrue("couldn't find custom rule", foundCustomEvent);
+    }
+
+    @Test
     public void ruleUidsCached() throws Exception {
         setupZenConfig();
         // one enabled automatic rule
@@ -2722,6 +2804,55 @@
     }
 
     @Test
+    public void testZenModeEventLog_policyAllowChannels() {
+        // when modes_api flag is on, ensure that any change in allow_channels gets logged,
+        // even when there are no other changes.
+        mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true);
+        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+        // Default zen config has allow channels = priority (aka on)
+        setupZenConfig();
+
+        // First just turn zen mode on
+        mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "",
+                Process.SYSTEM_UID, true);
+
+        // Now change only the channels part of the policy; want to confirm that this'll be
+        // reflected in the logs
+        ZenModeConfig newConfig = mZenModeHelper.mConfig.copy();
+        newConfig.allowPriorityChannels = false;
+        mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID,
+                true);
+
+        // Total events: one for turning on, one for changing policy
+        assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2);
+
+        // The first event is just turning DND on; make sure the policy is what we expect there
+        // before it changes in the next stage
+        assertThat(mZenModeEventLogger.getEventId(0))
+                .isEqualTo(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId());
+        DNDPolicyProto origDndProto = mZenModeEventLogger.getPolicyProto(0);
+        checkDndProtoMatchesSetupZenConfig(origDndProto);
+        assertThat(origDndProto.getAllowChannels().getNumber())
+                .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_PRIORITY);
+
+        // Second message where we change the policy:
+        //   - DND_POLICY_CHANGED (indicates only the policy changed and nothing else)
+        //   - rule type: unknown (it's a policy change, not a rule change)
+        //   - user action (because it comes from a "system" uid)
+        //   - change is in allow channels, and final policy
+        assertThat(mZenModeEventLogger.getEventId(1))
+                .isEqualTo(ZenModeEventLogger.ZenStateChangedEvent.DND_POLICY_CHANGED.getId());
+        assertThat(mZenModeEventLogger.getChangedRuleType(1))
+                .isEqualTo(DNDProtoEnums.UNKNOWN_RULE);
+        assertThat(mZenModeEventLogger.getIsUserAction(1)).isTrue();
+        assertThat(mZenModeEventLogger.getPackageUid(1)).isEqualTo(Process.SYSTEM_UID);
+        DNDPolicyProto dndProto = mZenModeEventLogger.getPolicyProto(1);
+        assertThat(dndProto.getAllowChannels().getNumber())
+                .isEqualTo(DNDProtoEnums.CHANNEL_TYPE_NONE);
+    }
+
+    @Test
     public void testUpdateConsolidatedPolicy_defaultRulesOnly() {
         setupZenConfig();
 
@@ -3416,6 +3547,7 @@
         return rule;
     }
 
+    // TODO: b/310620812 - Update setup methods to include allowChannels() when MODES_API is inlined
     private void setupZenConfig() {
         mZenModeHelper.mZenMode = ZEN_MODE_OFF;
         mZenModeHelper.mConfig.allowAlarms = false;
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
new file mode 100644
index 0000000..49efd1b
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class VibratorControlServiceTest {
+
+    private VibratorControlService mVibratorControlService;
+    private final Object mLock = new Object();
+
+    @Before
+    public void setUp() throws Exception {
+        mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock);
+    }
+
+    @Test
+    public void testRegisterVibratorController() throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController);
+
+        assertThat(fakeController.isLinkedToDeath).isTrue();
+    }
+
+    @Test
+    public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest()
+            throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController);
+        mVibratorControlService.unregisterVibratorController(fakeController);
+        assertThat(fakeController.isLinkedToDeath).isFalse();
+    }
+
+    @Test
+    public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest()
+            throws RemoteException {
+        FakeVibratorController fakeController1 = new FakeVibratorController();
+        FakeVibratorController fakeController2 = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController1);
+
+        mVibratorControlService.unregisterVibratorController(fakeController2);
+        assertThat(fakeController1.isLinkedToDeath).isTrue();
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
new file mode 100644
index 0000000..79abe21
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class VibratorControllerHolderTest {
+
+    private final FakeVibratorController mFakeVibratorController = new FakeVibratorController();
+    private VibratorControllerHolder mVibratorControllerHolder;
+
+    @Before
+    public void setUp() throws Exception {
+        mVibratorControllerHolder = new VibratorControllerHolder();
+    }
+
+    @Test
+    public void testSetVibratorController_linksVibratorControllerToDeath() throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        assertThat(mVibratorControllerHolder.getVibratorController())
+                .isEqualTo(mFakeVibratorController);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
+    }
+
+    @Test
+    public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath()
+            throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        mVibratorControllerHolder.setVibratorController(null);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
+        assertThat(mVibratorControllerHolder.getVibratorController()).isNull();
+    }
+
+    @Test
+    public void testBinderDied_withValidController_unlinksVibratorControllerToDeath()
+            throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        mVibratorControllerHolder.binderDied(mFakeVibratorController);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
+        assertThat(mVibratorControllerHolder.getVibratorController()).isNull();
+    }
+
+    @Test
+    public void testBinderDied_withInvalidController_ignoresRequest()
+            throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        FakeVibratorController imposterVibratorController = new FakeVibratorController();
+        mVibratorControllerHolder.binderDied(imposterVibratorController);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
+        assertThat(mVibratorControllerHolder.getVibratorController())
+                .isEqualTo(mFakeVibratorController);
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 3fce9e7..a105649 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -307,9 +307,10 @@
 
                     @Override
                     void addService(String name, IBinder service) {
-                        Object serviceInstance = service;
-                        mExternalVibratorService =
-                                (VibratorManagerService.ExternalVibratorService) serviceInstance;
+                        if (service instanceof VibratorManagerService.ExternalVibratorService) {
+                            mExternalVibratorService =
+                                    (VibratorManagerService.ExternalVibratorService) service;
+                        }
                     }
 
                     HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
new file mode 100644
index 0000000..7e23587
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 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.vibrator;
+
+import android.annotation.NonNull;
+import android.frameworks.vibrator.IVibratorController;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for
+ * testing.
+ */
+public final class FakeVibratorController extends IVibratorController.Stub {
+
+    public boolean isLinkedToDeath = false;
+
+    @Override
+    public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException {
+
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
+        super.linkToDeath(recipient, flags);
+        isLinkedToDeath = true;
+    }
+
+    @Override
+    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+        isLinkedToDeath = false;
+        return super.unlinkToDeath(recipient, flags);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
new file mode 100644
index 0000000..d2ef180
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.view.KeyEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for {@link DeferredKeyActionExecutor}.
+ *
+ * <p>Build/Install/Run: atest WmTests:DeferredKeyActionExecutorTests
+ */
+public final class DeferredKeyActionExecutorTests {
+
+    private DeferredKeyActionExecutor mKeyActionExecutor;
+
+    @Before
+    public void setUp() {
+        mKeyActionExecutor = new DeferredKeyActionExecutor();
+    }
+
+    @Test
+    public void queueKeyAction_actionNotExecuted() {
+        TestAction action = new TestAction();
+
+        mKeyActionExecutor.queueKeyAction(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action);
+
+        assertFalse(action.executed);
+    }
+
+    @Test
+    public void setActionsExecutable_afterActionQueued_actionExecuted() {
+        TestAction action = new TestAction();
+        mKeyActionExecutor.queueKeyAction(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action);
+
+        mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1);
+
+        assertTrue(action.executed);
+    }
+
+    @Test
+    public void queueKeyAction_alreadyExecutable_actionExecuted() {
+        TestAction action = new TestAction();
+        mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1);
+
+        mKeyActionExecutor.queueKeyAction(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action);
+
+        assertTrue(action.executed);
+    }
+
+    @Test
+    public void setActionsExecutable_afterActionQueued_downTimeMismatch_actionNotExecuted() {
+        TestAction action1 = new TestAction();
+        mKeyActionExecutor.queueKeyAction(
+                KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action1);
+
+        mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 2);
+
+        assertFalse(action1.executed);
+
+        TestAction action2 = new TestAction();
+        mKeyActionExecutor.queueKeyAction(
+                KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 2, action2);
+
+        assertFalse(action1.executed);
+        assertTrue(action2.executed);
+    }
+
+    @Test
+    public void queueKeyAction_afterSetExecutable_downTimeMismatch_actionNotExecuted() {
+        TestAction action = new TestAction();
+        mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1);
+
+        mKeyActionExecutor.queueKeyAction(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 2, action);
+
+        assertFalse(action.executed);
+    }
+
+    static class TestAction implements Runnable {
+        public boolean executed;
+
+        @Override
+        public void run() {
+            executed = true;
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java b/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java
index c12dcdd..242996b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/TestActivity.java
@@ -16,17 +16,11 @@
 
 package com.android.server.wm.utils;
 
-import static android.view.WindowInsets.Type.displayCutout;
-import static android.view.WindowInsets.Type.systemBars;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import android.app.Activity;
 import android.app.KeyguardManager;
 import android.os.Bundle;
-import android.view.WindowInsetsController;
-import android.view.WindowManager;
 import android.widget.FrameLayout;
 
 import androidx.annotation.Nullable;
@@ -35,7 +29,6 @@
  * TestActivity that will ensure it dismisses keyguard and shows as a fullscreen activity.
  */
 public class TestActivity extends Activity {
-    private static final int sTypeMask = systemBars() | displayCutout();
     private FrameLayout mParentLayout;
 
     @Override
@@ -48,13 +41,6 @@
                 FrameLayout.LayoutParams.MATCH_PARENT);
         setContentView(mParentLayout, layoutParams);
 
-        WindowInsetsController windowInsetsController = getWindow().getInsetsController();
-        windowInsetsController.hide(sTypeMask);
-        WindowManager.LayoutParams params = getWindow().getAttributes();
-        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        getWindow().setAttributes(params);
-        getWindow().setDecorFitsSystemWindows(false);
-
         final KeyguardManager keyguardManager = getInstrumentation().getContext().getSystemService(
                 KeyguardManager.class);
         if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp
index ddec8fa..a487799 100644
--- a/tests/Internal/Android.bp
+++ b/tests/Internal/Android.bp
@@ -27,3 +27,16 @@
     platform_apis: true,
     test_suites: ["device-tests"],
 }
+
+android_ravenwood_test {
+    name: "InternalTestsRavenwood",
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.rules",
+        "platform-test-annotations",
+    ],
+    srcs: [
+        "src/com/android/internal/util/ParcellingTests.java",
+    ],
+    auto_gen_config: true,
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
index 2255345..3bcabcb 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
@@ -15,9 +15,6 @@
  */
 package com.android.hoststubgen.nativesubstitution;
 
-import android.os.IBinder;
-
-import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
@@ -143,12 +140,6 @@
     public static void nativeMarkSensitive(long nativePtr) {
         getInstance(nativePtr).mSensitive = true;
     }
-    public static void nativeMarkForBinder(long nativePtr, IBinder binder) {
-        throw new RuntimeException("Not implemented yet");
-    }
-    public static boolean nativeIsForRpc(long nativePtr) {
-        throw new RuntimeException("Not implemented yet");
-    }
     public static int nativeDataSize(long nativePtr) {
         return getInstance(nativePtr).mSize;
     }
@@ -236,9 +227,6 @@
     public static int nativeWriteDouble(long nativePtr, double val) {
         return nativeWriteLong(nativePtr, Double.doubleToLongBits(val));
     }
-    public static void nativeSignalExceptionForError(int error) {
-        throw new RuntimeException("Not implemented yet");
-    }
 
     private static int align4(int val) {
         return ((val + 3) / 4) * 4;
@@ -256,12 +244,6 @@
         // Just reuse String8
         nativeWriteString8(nativePtr, val);
     }
-    public static void nativeWriteStrongBinder(long nativePtr, IBinder val) {
-        throw new RuntimeException("Not implemented yet");
-    }
-    public static void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val) {
-        throw new RuntimeException("Not implemented yet");
-    }
 
     public static byte[] nativeCreateByteArray(long nativePtr) {
         return nativeReadBlob(nativePtr);
@@ -348,12 +330,6 @@
     public static String nativeReadString16(long nativePtr) {
         return nativeReadString8(nativePtr);
     }
-    public static IBinder nativeReadStrongBinder(long nativePtr) {
-        throw new RuntimeException("Not implemented yet");
-    }
-    public static FileDescriptor nativeReadFileDescriptor(long nativePtr) {
-        throw new RuntimeException("Not implemented yet");
-    }
 
     public static byte[] nativeMarshall(long nativePtr) {
         var p = getInstance(nativePtr);
@@ -367,13 +343,6 @@
         p.mPos += length;
         p.updateSize();
     }
-    public static int nativeCompareData(long thisNativePtr, long otherNativePtr) {
-        throw new RuntimeException("Not implemented yet");
-    }
-    public static boolean nativeCompareDataInRange(
-            long ptrA, int offsetA, long ptrB, int offsetB, int length) {
-        throw new RuntimeException("Not implemented yet");
-    }
     public static void nativeAppendFrom(
             long thisNativePtr, long otherNativePtr, int srcOffset, int length) {
         var dst = getInstance(thisNativePtr);
@@ -397,28 +366,4 @@
         // Assume false for now, because we don't support writing FDs yet.
         return false;
     }
-    public static void nativeWriteInterfaceToken(long nativePtr, String interfaceName) {
-        throw new RuntimeException("Not implemented yet");
-    }
-    public static void nativeEnforceInterface(long nativePtr, String interfaceName) {
-        throw new RuntimeException("Not implemented yet");
-    }
-
-    public static boolean nativeReplaceCallingWorkSourceUid(
-            long nativePtr, int workSourceUid) {
-        throw new RuntimeException("Not implemented yet");
-    }
-    public static int nativeReadCallingWorkSourceUid(long nativePtr) {
-        throw new RuntimeException("Not implemented yet");
-    }
-
-    public static long nativeGetOpenAshmemSize(long nativePtr) {
-        throw new RuntimeException("Not implemented yet");
-    }
-    public static long getGlobalAllocSize() {
-        throw new RuntimeException("Not implemented yet");
-    }
-    public static long getGlobalAllocCount() {
-        throw new RuntimeException("Not implemented yet");
-    }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
index 207ba52..910bf59 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
@@ -46,3 +46,7 @@
  */
 class InvalidAnnotationException(message: String) : Exception(message), UserErrorException
 
+/**
+ * We use this for general "user" errors.
+ */
+class HostStubGenUserErrorException(message: String) : Exception(message), UserErrorException
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 4db583f..97e09b8 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -128,7 +128,7 @@
         }
 
         val end = System.currentTimeMillis()
-        log.v("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
+        log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
         return allClasses
     }
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
index 9df0489..6b01d48 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
@@ -15,10 +15,10 @@
  */
 package com.android.hoststubgen
 
-class HostStubGenErrors {
-    fun onErrorFound(message: String) {
-        // For now, we just throw as soon as any error is found, but eventually we should keep
+open class HostStubGenErrors {
+    open fun onErrorFound(message: String) {
+        // TODO: For now, we just throw as soon as any error is found, but eventually we should keep
         // all errors and print them at the end.
-        throw RuntimeException(message)
+        throw HostStubGenUserErrorException(message)
     }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 6262fa1..0579c2b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -196,6 +196,29 @@
     }
 }
 
+enum class Visibility {
+    PRIVATE,
+    PACKAGE_PRIVATE,
+    PROTECTED,
+    PUBLIC;
+
+    companion object {
+        fun fromAccess(access: Int): Visibility {
+            if ((access and Opcodes.ACC_PUBLIC) != 0) {
+                return PUBLIC
+            }
+            if ((access and Opcodes.ACC_PROTECTED) != 0) {
+                return PROTECTED
+            }
+            if ((access and Opcodes.ACC_PRIVATE) != 0) {
+                return PRIVATE
+            }
+
+            return PACKAGE_PRIVATE
+        }
+    }
+}
+
 fun ClassNode.isEnum(): Boolean {
     return (this.access and Opcodes.ACC_ENUM) != 0
 }
@@ -212,6 +235,10 @@
     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
 }
 
+fun MethodNode.isStatic(): Boolean {
+    return (this.access and Opcodes.ACC_STATIC) != 0
+}
+
 fun FieldNode.isEnum(): Boolean {
     return (this.access and Opcodes.ACC_ENUM) != 0
 }
@@ -220,6 +247,19 @@
     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
 }
 
+fun ClassNode.getVisibility(): Visibility {
+    return Visibility.fromAccess(this.access)
+}
+
+fun MethodNode.getVisibility(): Visibility {
+    return Visibility.fromAccess(this.access)
+}
+
+fun FieldNode.getVisibility(): Visibility {
+    return Visibility.fromAccess(this.access)
+}
+
+
 /*
 
 Dump of the members of TinyFrameworkEnumSimple:
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 18dd4c2..21cfd4b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -17,8 +17,8 @@
 
 import com.android.hoststubgen.HostStubGenErrors
 import com.android.hoststubgen.LogLevel
-import com.android.hoststubgen.asm.UnifiedVisitor
 import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.UnifiedVisitor
 import com.android.hoststubgen.asm.getPackageNameFromClassName
 import com.android.hoststubgen.asm.resolveClassName
 import com.android.hoststubgen.asm.toJvmClassName
@@ -178,7 +178,9 @@
         log.d("visitMethod: %s%s [%x] [%s] Policy: %s", name, descriptor, access, signature, p)
 
         log.withIndent {
-            // If it's a substitute-to method, then skip.
+            // If it's a substitute-from method, then skip (== remove).
+            // Instead of this method, we rename the substitute-to method with the original
+            // name, in the "Maybe rename the method" part below.
             val policy = filter.getPolicyForMethod(currentClassName, name, descriptor)
             if (policy.policy.isSubstitute) {
                 log.d("Skipping %s%s %s", name, descriptor, policy)
@@ -191,9 +193,19 @@
 
             // Maybe rename the method.
             val newName: String
-            val substituteTo = filter.getRenameTo(currentClassName, name, descriptor)
-            if (substituteTo != null) {
-                newName = substituteTo
+            val renameTo = filter.getRenameTo(currentClassName, name, descriptor)
+            if (renameTo != null) {
+                newName = renameTo
+
+                // It's confusing, but here, `newName` is the original method name
+                // (the one with the @substitute/replace annotation).
+                // `name` is the name of the method we're currently visiting, so it's usually a
+                // "...$ravewnwood" name.
+                if (!checkSubstitutionMethodCompatibility(
+                        classes, currentClassName, newName, name, descriptor, options.errors)) {
+                    return null
+                }
+
                 log.v("Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
                         newName, policy)
             } else {
@@ -203,12 +215,12 @@
 
             // Let subclass update the flag.
             // But note, we only use it when calling the super's method,
-            // but not for visitMethodInner(), beucase when subclass wants to change access,
+            // but not for visitMethodInner(), because when subclass wants to change access,
             // it can do so inside visitMethodInner().
             val newAccess = updateAccessFlags(access, name, descriptor)
 
             val ret = visitMethodInner(access, newName, descriptor, signature, exceptions, policy,
-                substituteTo != null,
+                renameTo != null,
                 super.visitMethod(newAccess, newName, descriptor, signature, exceptions))
 
             ret?.let {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt
new file mode 100644
index 0000000..9d66c32
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.visitors
+
+import com.android.hoststubgen.HostStubGenErrors
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.getVisibility
+import com.android.hoststubgen.asm.isStatic
+
+/**
+ * Make sure substitution from and to methods have matching definition.
+ * (static-ness, visibility.)
+ */
+fun checkSubstitutionMethodCompatibility(
+    classes: ClassNodes,
+    className: String,
+    fromMethodName: String, // the one with the annotation
+    toMethodName: String, // the one with either a "_host" or "$ravenwood" prefix. (typically)
+    descriptor: String,
+    errors: HostStubGenErrors,
+): Boolean {
+    val from = classes.findMethod(className, fromMethodName, descriptor)
+    if (from == null) {
+        errors.onErrorFound(
+            "Substitution-from method not found: $className.$fromMethodName$descriptor")
+        return false
+    }
+    val to = classes.findMethod(className, toMethodName, descriptor)
+    if (to == null) {
+        // This shouldn't happen, because the visitor visited this method...
+        errors.onErrorFound(
+            "Substitution-to method not found: $className.$toMethodName$descriptor")
+        return false
+    }
+
+    if (from.isStatic() != to.isStatic()) {
+        errors.onErrorFound(
+            "Substitution method must have matching static-ness: " +
+                    "$className.$fromMethodName$descriptor")
+        return false
+    }
+    if (from.getVisibility().ordinal > to.getVisibility().ordinal) {
+        errors.onErrorFound(
+            "Substitution method cannot have smaller visibility than original: " +
+                    "$className.$fromMethodName$descriptor")
+        return false
+    }
+
+    return true
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index 9274a96..416b782 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -192,18 +192,24 @@
         }
 
         log.withIndent {
+            var willThrow = false
+            if (policy.policy == FilterPolicy.Throw) {
+                log.v("Making method throw...")
+                willThrow = true
+                innerVisitor = ThrowingMethodAdapter(
+                    access, name, descriptor, signature, exceptions, innerVisitor)
+                    .withAnnotation(HostStubGenProcessedAsThrow.CLASS_DESCRIPTOR)
+            }
             if ((access and Opcodes.ACC_NATIVE) != 0 && nativeSubstitutionClass != null) {
                 log.v("Rewriting native method...")
                 return NativeSubstitutingMethodAdapter(
                         access, name, descriptor, signature, exceptions, innerVisitor)
                     .withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR)
             }
-            if (policy.policy == FilterPolicy.Throw) {
-                log.v("Making method throw...")
-                return ThrowingMethodAdapter(
-                        access, name, descriptor, signature, exceptions, innerVisitor)
-                    .withAnnotation(HostStubGenProcessedAsThrow.CLASS_DESCRIPTOR)
+            if (willThrow) {
+                return innerVisitor
             }
+
             if (policy.policy == FilterPolicy.Ignore) {
                 when (Type.getReturnType(descriptor)) {
                     Type.VOID_TYPE -> {
@@ -218,8 +224,8 @@
                 }
             }
         }
-        if (substituted && innerVisitor != null) {
-            innerVisitor.withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR)
+        if (substituted) {
+            innerVisitor?.withAnnotation(HostStubGenProcessedAsSubstitute.CLASS_DESCRIPTOR)
         }
 
         return innerVisitor
@@ -309,13 +315,13 @@
             next: MethodVisitor?
     ) : MethodVisitor(OPCODE_VERSION, next) {
         override fun visitCode() {
-            super.visitCode()
-
             throw RuntimeException("NativeSubstitutingMethodVisitor should be called on " +
                     " native method, where visitCode() shouldn't be called.")
         }
 
         override fun visitEnd() {
+            super.visitCode()
+
             var targetDescriptor = descriptor
             var argOffset = 0
 
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 3956893..70f56ae 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -1817,7 +1817,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 8, attributes: 2
+  interfaces: 0, fields: 1, methods: 10, attributes: 2
   int value;
     descriptor: I
     flags: (0x0000)
@@ -1904,6 +1904,24 @@
         Start  Length  Slot  Name   Signature
             0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
             0       6     1   arg   I
+
+  public static native void nativeStillNotSupported();
+    descriptor: ()V
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public static void nativeStillNotSupported_should_be_like_this();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeInvisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index ebe1422..b0db483 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -1538,7 +1538,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 8, attributes: 3
+  interfaces: 0, fields: 1, methods: 9, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
@@ -1654,6 +1654,22 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static void nativeStillNotSupported_should_be_like_this();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 4cb2a9f..112f69e 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -2220,7 +2220,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 8, attributes: 3
+  interfaces: 0, fields: 1, methods: 10, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
@@ -2375,6 +2375,50 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static void nativeStillNotSupported();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeStillNotSupported
+         x: ldc           #x                 // String ()V
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public static void nativeStillNotSupported_should_be_like_this();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index ebe1422..b0db483 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -1538,7 +1538,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 8, attributes: 3
+  interfaces: 0, fields: 1, methods: 9, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
@@ -1654,6 +1654,22 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static void nativeStillNotSupported_should_be_like_this();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index 49be4db..2357844 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -2727,7 +2727,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 9, attributes: 3
+  interfaces: 0, fields: 1, methods: 11, attributes: 3
   int value;
     descriptor: I
     flags: (0x0000)
@@ -2774,10 +2774,15 @@
     descriptor: (I)I
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
-      stack=1, locals=1, args_size=1
-         x: iload_0
-         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
-         x: ireturn
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeAddTwo
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_0
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+        x: ireturn
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
@@ -2814,10 +2819,15 @@
     flags: (0x0009) ACC_PUBLIC, ACC_STATIC
     Code:
       stack=4, locals=4, args_size=2
-         x: lload_0
-         x: lload_2
-         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
-         x: lreturn
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeLongPlus
+         x: ldc           #x                 // String (JJ)J
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: lload_0
+        x: lload_2
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+        x: lreturn
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
@@ -2880,11 +2890,16 @@
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
-      stack=2, locals=2, args_size=2
-         x: aload_0
-         x: iload_1
-         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
-         x: ireturn
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeNonStaticAddToValue
+         x: ldc           #x                 // String (I)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: iload_1
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeNonStaticAddToValue:(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;I)I
+        x: ireturn
     RuntimeVisibleAnnotations:
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
@@ -2917,6 +2932,60 @@
         com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static void nativeStillNotSupported();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                 // String nativeStillNotSupported
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+        x: ldc           #x                 // String nativeStillNotSupported
+        x: ldc           #x                 // String ()V
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+        x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: ldc           #x                 // String Unreachable
+        x: invokespecial #x                // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsThrow
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+    RuntimeInvisibleAnnotations:
+      x: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public static void nativeStillNotSupported_should_be_like_this();
+    descriptor: ()V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+         x: ldc           #x                // String nativeStillNotSupported_should_be_like_this
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: new           #x                 // class java/lang/RuntimeException
+        x: dup
+        x: invokespecial #x                // Method java/lang/RuntimeException."<init>":()V
+        x: athrow
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 }
 SourceFile: "TinyFrameworkNative.java"
 RuntimeVisibleAnnotations:
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
index e7b5d9f..5a5e22d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
@@ -16,6 +16,7 @@
 package com.android.hoststubgen.test.tinyframework;
 
 import android.hosttest.annotation.HostSideTestNativeSubstitutionClass;
+import android.hosttest.annotation.HostSideTestThrow;
 import android.hosttest.annotation.HostSideTestWholeClassStub;
 
 @HostSideTestWholeClassStub
@@ -44,4 +45,11 @@
     public int nativeNonStaticAddToValue_should_be_like_this(int arg) {
         return TinyFrameworkNative_host.nativeNonStaticAddToValue(this, arg);
     }
+
+    @HostSideTestThrow
+    public static native void nativeStillNotSupported();
+
+    public static void nativeStillNotSupported_should_be_like_this() {
+        throw new RuntimeException();
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index d350105..fc6b862 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -17,6 +17,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.fail;
+
 import com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.SubClass;
 
 import org.junit.Rule;
@@ -158,6 +160,32 @@
         assertThat(instance.nativeNonStaticAddToValue(3)).isEqualTo(8);
     }
 
+
+    @Test
+    public void testSubstituteNativeWithThrow() throws Exception {
+        // We can't use TinyFrameworkNative.nativeStillNotSupported() directly in this class,
+        // because @Throw implies @Keep (not @Stub), and we currently compile this test
+        // against the stub jar (so it won't contain @Throw methods).
+        //
+        // But the method exists at runtime, so we can use reflections to call it.
+        //
+        // In the real Ravenwood environment, we don't use HostStubGen's stub jar at all,
+        // so it's not a problem.
+
+        final var clazz = TinyFrameworkNative.class;
+        final var method = clazz.getMethod("nativeStillNotSupported");
+
+        try {
+            method.invoke(null);
+
+            fail("java.lang.reflect.InvocationTargetException expected");
+
+        } catch (java.lang.reflect.InvocationTargetException e) {
+            var inner = e.getCause();
+            assertThat(inner.getMessage()).contains("not supported on the host side");
+        }
+    }
+
     @Test
     public void testExitLog() {
         thrown.expect(RuntimeException.class);
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/AsmUtilsTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt
similarity index 64%
rename from tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/AsmUtilsTest.kt
rename to tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt
index 66624d1..6b46c84 100644
--- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/AsmUtilsTest.kt
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt
@@ -13,11 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.hoststubgen.utils
+package com.android.hoststubgen.asm
 
-import com.android.hoststubgen.asm.getDirectOuterClassName
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
+import org.objectweb.asm.Opcodes.ACC_PRIVATE
+import org.objectweb.asm.Opcodes.ACC_PROTECTED
+import org.objectweb.asm.Opcodes.ACC_PUBLIC
+import org.objectweb.asm.Opcodes.ACC_STATIC
 
 class AsmUtilsTest {
     private fun checkGetDirectOuterClassName(input: String, expected: String?) {
@@ -31,4 +34,16 @@
         checkGetDirectOuterClassName("a.b.c\$x", "a.b.c")
         checkGetDirectOuterClassName("a.b.c\$x\$y", "a.b.c\$x")
     }
+
+    @Test
+    fun testVisibility() {
+        fun test(access: Int, expected: Visibility) {
+            assertThat(Visibility.fromAccess(access)).isEqualTo(expected)
+        }
+
+        test(ACC_PUBLIC or ACC_STATIC, Visibility.PUBLIC)
+        test(ACC_PRIVATE or ACC_STATIC, Visibility.PRIVATE)
+        test(ACC_PROTECTED or ACC_STATIC, Visibility.PROTECTED)
+        test(ACC_STATIC, Visibility.PACKAGE_PRIVATE)
+    }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt
new file mode 100644
index 0000000..0ea90ed
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.visitors
+
+import com.android.hoststubgen.HostStubGenErrors
+import com.android.hoststubgen.asm.ClassNodes
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.MethodNode
+
+class HelperTest {
+    @Test
+    fun testCheckSubstitutionMethodCompatibility() {
+        val errors = object : HostStubGenErrors() {
+            override fun onErrorFound(message: String) {
+                // Don't throw
+            }
+        }
+
+        val cn = ClassNode().apply {
+            name = "ClassName"
+            methods = ArrayList()
+        }
+
+        val descriptor = "()V"
+
+        val staticPublic = MethodNode().apply {
+            name = "staticPublic"
+            access = Opcodes.ACC_STATIC or Opcodes.ACC_PUBLIC
+            desc = descriptor
+            cn.methods.add(this)
+        }
+
+        val staticPrivate = MethodNode().apply {
+            name = "staticPrivate"
+            access = Opcodes.ACC_STATIC or Opcodes.ACC_PRIVATE
+            desc = descriptor
+            cn.methods.add(this)
+        }
+
+        val nonStaticPublic = MethodNode().apply {
+            name = "nonStaticPublic"
+            access = Opcodes.ACC_PUBLIC
+            desc = descriptor
+            cn.methods.add(this)
+        }
+
+        val nonStaticPProtected = MethodNode().apply {
+            name = "nonStaticPProtected"
+            access = 0
+            desc = descriptor
+            cn.methods.add(this)
+        }
+
+        val classes = ClassNodes().apply {
+            addClass(cn)
+        }
+
+        fun check(from: MethodNode?, to: MethodNode?, expected: Boolean) {
+            assertThat(checkSubstitutionMethodCompatibility(
+                classes,
+                cn.name,
+                (from?.name ?: "**nonexistentmethodname**"),
+                (to?.name ?: "**nonexistentmethodname**"),
+                descriptor,
+                errors,
+            )).isEqualTo(expected)
+        }
+
+        check(staticPublic, staticPublic, true)
+        check(staticPrivate, staticPrivate, true)
+        check(nonStaticPublic, nonStaticPublic, true)
+        check(nonStaticPProtected, nonStaticPProtected, true)
+
+        check(staticPublic, null, false)
+        check(null, staticPublic, false)
+
+        check(staticPublic, nonStaticPublic, false)
+        check(nonStaticPublic, staticPublic, false)
+
+        check(staticPublic, staticPrivate, false)
+        check(staticPrivate, staticPublic, true)
+
+        check(nonStaticPublic, nonStaticPProtected, false)
+        check(nonStaticPProtected, nonStaticPublic, true)
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
index c7007db..222c874 100755
--- a/tools/hoststubgen/scripts/run-all-tests.sh
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -25,6 +25,7 @@
   HostStubGenTest-framework-all-test-host-test
   hoststubgen-test-tiny-test
   CtsUtilTestCasesRavenwood
+  CtsOsTestCasesRavenwood # This one uses native sustitution, so let's run it too.
 )
 
 MUST_BUILD_MODULES=(
@@ -55,4 +56,4 @@
 # These tests should all pass.
 run atest $ATEST_ARGS ${READY_TEST_MODULES[*]}
 
-echo ""${0##*/}" finished, with no failures. Ready to submit!"
\ No newline at end of file
+echo ""${0##*/}" finished, with no failures. Ready to submit!"