Merge "Mark archived package as not installed." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 20f879c..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 {
@@ -848,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/core/api/current.txt b/core/api/current.txt
index 2a16d3e..5310fdc 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";
@@ -18808,6 +18810,7 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AWB_AVAILABLE_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AWB_LOCK_AVAILABLE;
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AF;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AWB;
@@ -19091,6 +19094,7 @@
     field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH = 2; // 0x2
     field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4; // 0x4
     field public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5; // 0x5
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY = 6; // 0x6
     field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2; // 0x2
     field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0; // 0x0
     field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1; // 0x1
@@ -19156,6 +19160,8 @@
     field public static final int CONTROL_EXTENDED_SCENE_MODE_BOKEH_CONTINUOUS = 2; // 0x2
     field public static final int CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE = 1; // 0x1
     field public static final int CONTROL_EXTENDED_SCENE_MODE_DISABLED = 0; // 0x0
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE = 1; // 0x1
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE = 0; // 0x0
     field public static final int CONTROL_MODE_AUTO = 1; // 0x1
     field public static final int CONTROL_MODE_OFF = 0; // 0x0
     field public static final int CONTROL_MODE_OFF_KEEP_STATE = 3; // 0x3
@@ -19476,6 +19482,7 @@
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EXTENDED_SCENE_MODE;
+    field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_LOW_LIGHT_BOOST_STATE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
@@ -40628,7 +40635,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;
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/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/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index fe95a2a..9125856 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1332,6 +1332,27 @@
             new Key<Boolean>("android.control.autoframingAvailable", boolean.class);
 
     /**
+     * <p>The operating luminance range of low light boost measured in lux (lx).</p>
+     * <p><b>Range of valid values:</b><br></p>
+     * <p>The lower bound indicates the lowest scene luminance value the AE mode
+     * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' can operate within. Scenes of lower luminance
+     * than this may receive less brightening, increased noise, or artifacts.</p>
+     * <p>The upper bound indicates the luminance threshold at the point when the mode is enabled.
+     * For example, 'Range[0.3, 30.0]' defines 0.3 lux being the lowest scene luminance the
+     * mode can reliably support. 30.0 lux represents the threshold when this mode is
+     * activated. Scenes measured at less than or equal to 30 lux will activate low light
+     * boost.</p>
+     * <p>If this key is defined, then the AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' will
+     * also be present.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final Key<android.util.Range<Float>> CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE =
+            new Key<android.util.Range<Float>>("android.control.lowLightBoostInfoLuminanceRange", new TypeReference<android.util.Range<Float>>() {{ }});
+
+    /**
      * <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
      * device.</p>
      * <p>Full-capability camera devices must always support OFF; camera devices that support
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 507e814..530f75e 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2333,6 +2333,46 @@
      */
     public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5;
 
+    /**
+     * <p>Like 'ON' but applies additional brightness boost in low light scenes.</p>
+     * <p>When the scene lighting conditions are within the range defined by
+     * {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange} this mode will apply additional
+     * brightness boost.</p>
+     * <p>This mode will automatically adjust the intensity of low light boost applied
+     * according to the scene lighting conditions. A darker scene will receive more boost
+     * while a brighter scene will receive less boost.</p>
+     * <p>This mode can ignore the set target frame rate to allow more light to be captured
+     * which can result in choppier motion. The frame rate can extend to lower than the
+     * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges} but will not go below 10 FPS. This mode
+     * can also increase the sensor sensitivity gain which can result in increased luma
+     * and chroma noise. The sensor sensitivity gain can extend to higher values beyond
+     * {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}. This mode may also apply additional
+     * processing to recover details in dark and bright areas of the image,and noise
+     * reduction at high sensitivity gain settings to manage the trade-off between light
+     * sensitivity and capture noise.</p>
+     * <p>This mode is restricted to two output surfaces. One output surface type can either
+     * be SurfaceView or TextureView. Another output surface type can either be MediaCodec
+     * or MediaRecorder. This mode cannot be used with a target FPS range higher than 30
+     * FPS.</p>
+     * <p>If the session configuration is not supported, the AE mode reported in the
+     * CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p>
+     * <p>The application can observe the CapturerResult field
+     * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} to determine when low light boost is 'ACTIVE' or
+     * 'INACTIVE'.</p>
+     * <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the
+     * upper bound lux value defined by {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange}.
+     * This mode will be 'INACTIVE' once the scene lighting condition is greater than the
+     * upper bound lux value defined by {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange}.</p>
+     *
+     * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
+     * @see CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE
+     * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+     * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
+     * @see CaptureRequest#CONTROL_AE_MODE
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final int CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY = 6;
+
     //
     // Enumeration values for CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
     //
@@ -4074,6 +4114,24 @@
     public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2;
 
     //
+    // Enumeration values for CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+    //
+
+    /**
+     * <p>The AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' is enabled but not applied.</p>
+     * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final int CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE = 0;
+
+    /**
+     * <p>The AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' is enabled and applied.</p>
+     * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE
+     */
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final int CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE = 1;
+
+    //
     // Enumeration values for CaptureResult#FLASH_STATE
     //
 
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 35f295a..ab4406c3 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2814,6 +2814,31 @@
             new Key<Integer>("android.control.autoframingState", int.class);
 
     /**
+     * <p>Current state of the low light boost AE mode.</p>
+     * <p>When low light boost is enabled by setting the AE mode to
+     * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light
+     * boost when the light level threshold is exceeded.</p>
+     * <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can
+     * indicate when it is not being applied by returning 'INACTIVE'.</p>
+     * <p>This key will be absent from the CaptureResult if AE mode is not set to
+     * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.</p>
+     * <p><b>Possible values:</b></p>
+     * <ul>
+     *   <li>{@link #CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE INACTIVE}</li>
+     *   <li>{@link #CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE ACTIVE}</li>
+     * </ul>
+     *
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * @see #CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE
+     * @see #CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST)
+    public static final Key<Integer> CONTROL_LOW_LIGHT_BOOST_STATE =
+            new Key<Integer>("android.control.lowLightBoostState", int.class);
+
+    /**
      * <p>Operation mode for edge
      * enhancement.</p>
      * <p>Edge enhancement improves sharpness and details in the captured image. OFF means
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/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/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/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/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/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index d7b306c..03170a3 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -57,10 +57,13 @@
 
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
+
+        tapl.enableBlockTimeout(true)
     }
 
     @Test
     open fun enterSplitScreenByDragFromAllApps() {
+        tapl.showTaskbarIfHidden()
         tapl.launchedAppState.taskbar
             .openAllApps()
             .getAppIcon(secondaryApp.appName)
@@ -72,5 +75,6 @@
     fun teardown() {
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
+        tapl.enableBlockTimeout(false)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 8134fdd..479d01d 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -59,10 +59,13 @@
 
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
+
+        tapl.enableBlockTimeout(true)
     }
 
     @Test
     open fun enterSplitScreenByDragFromShortcut() {
+        tapl.showTaskbarIfHidden()
         tapl.launchedAppState.taskbar
             .getAppIcon(secondaryApp.appName)
             .openDeepShortcutMenu()
@@ -83,6 +86,7 @@
     fun teardwon() {
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
+        tapl.enableBlockTimeout(false)
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 3417744..625c56b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -54,6 +54,8 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
 
+        tapl.enableBlockTimeout(true)
+
         tapl.goHome()
         SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
         primaryApp.launchViaIntent(wmHelper)
@@ -61,6 +63,7 @@
 
     @Test
     open fun enterSplitScreenByDragFromTaskbar() {
+        tapl.showTaskbarIfHidden()
         tapl.launchedAppState.taskbar
             .getAppIcon(secondaryApp.appName)
             .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
@@ -71,6 +74,7 @@
     fun teardown() {
         primaryApp.exit(wmHelper)
         secondaryApp.exit(wmHelper)
+        tapl.enableBlockTimeout(false)
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
index 394864a..5c43cbd 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt
@@ -23,6 +23,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -42,8 +43,10 @@
             setup {
                 tapl.goHome()
                 primaryApp.launchViaIntent(wmHelper)
+                tapl.enableBlockTimeout(true)
             }
             transitions {
+                tapl.showTaskbarIfHidden()
                 tapl.launchedAppState.taskbar
                     .openAllApps()
                     .getAppIcon(secondaryApp.appName)
@@ -57,6 +60,11 @@
         Assume.assumeTrue(tapl.isTablet)
     }
 
+    @After
+    fun after() {
+        tapl.enableBlockTimeout(false)
+    }
+
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
index 3b3be84..15ad0c1 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt
@@ -23,6 +23,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -42,13 +43,20 @@
         Assume.assumeTrue(tapl.isTablet)
     }
 
+    @After
+    fun after() {
+        tapl.enableBlockTimeout(false)
+    }
+
     protected val thisTransition: FlickerBuilder.() -> Unit = {
         setup {
             tapl.goHome()
             SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
             primaryApp.launchViaIntent(wmHelper)
+            tapl.enableBlockTimeout(true)
         }
         transitions {
+            tapl.showTaskbarIfHidden()
             tapl.launchedAppState.taskbar
                 .getAppIcon(secondaryApp.appName)
                 .openDeepShortcutMenu()
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
index eff3559..ca8adb1 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt
@@ -23,6 +23,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -44,6 +45,7 @@
                 primaryApp.launchViaIntent(wmHelper)
             }
             transitions {
+                tapl.showTaskbarIfHidden()
                 tapl.launchedAppState.taskbar
                     .getAppIcon(secondaryApp.appName)
                     .dragToSplitscreen(secondaryApp.packageName, primaryApp.packageName)
@@ -54,6 +56,12 @@
     @Before
     fun before() {
         Assume.assumeTrue(tapl.isTablet)
+        tapl.enableBlockTimeout(true)
+    }
+
+    @After
+    fun after() {
+        tapl.enableBlockTimeout(false)
     }
 
     companion object {
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/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/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/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/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/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/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 fde5357..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
@@ -68,8 +68,8 @@
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.monet.Style
-import com.android.systemui.plugins.ClockController
 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
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/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/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/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/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/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/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/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/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 a75bba6..6a6ae38 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -92,12 +92,6 @@
 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/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/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 2f7d99f..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 {
@@ -2007,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);
             }
@@ -2098,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;
 
 
 
@@ -2745,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 e503f1f..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
@@ -1547,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();
@@ -1829,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,
@@ -2149,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,
@@ -2274,7 +2390,8 @@
                             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) {
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 a078d08..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;
@@ -33,6 +49,7 @@
 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;
@@ -1115,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/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/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/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/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/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/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/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/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/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/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/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!"