Merge "Return `RESULT_CONTINUE` in `DesktopModeLaunchParamsModifier`" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
index e649485..e82df12 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING
@@ -41,10 +41,10 @@
             ]
         },
         {
-            "name": "CtsHostsideNetworkTests",
+            "name": "CtsHostsideNetworkPolicyTests",
             "options": [
-                {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
-                {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
+                {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"},
+                {"include-filter": "com.android.cts.netpolicy.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"}
             ]
         },
         {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index f1e44cc..1df8f63 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1104,6 +1104,10 @@
             return true;
         }
 
+        if (mDataDir == null) {
+            return false;
+        }
+
         // Temporarily disable logging of disk reads on the Looper thread as this is necessary -
         // and the loader will access the directory anyway if we don't check it.
         StrictMode.ThreadPolicy oldThreadPolicy = allowThreadDiskReads();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4d7e29b..05a2aec 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -9770,6 +9770,12 @@
      * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}.
      * <p>
      *
+     * <p>
+     * Starting at {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V} the
+     * {@link Notification#FLAG_NO_CLEAR NO_CLEAR flag} will be set for valid MediaStyle
+     * notifications.
+     * <p>
+     *
      * To use this style with your Notification, feed it to
      * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
      * <pre class="prettyprint">
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index f092945..9b3fb5c 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1197,6 +1197,24 @@
     /**
      * Callback called when a particular foreground service type has timed out.
      *
+     * <p>This callback is meant to give the app a small grace period of a few seconds to finish
+     * the foreground service of the offending type - if it fails to do so, the app will be
+     * declared an ANR.
+     *
+     * <p>The foreground service of the offending type can be stopped within the time limit by
+     * {@link android.app.Service#stopSelf()},
+     * {@link android.content.Context#stopService(android.content.Intent)} or their overloads.
+     * {@link android.app.Service#stopForeground(int)} can be used as well, which demotes the
+     * service to a "background" service, which will soon be stopped by the system.
+     *
+     * <p>The specific time limit for each type (if one exists) is mentioned in the documentation
+     * for that foreground service type.
+     *
+     * <p>Note: time limits are restricted to a rolling 24-hour window - for example, if a
+     * foreground service type has a time limit of 6 hours, that time counter begins as soon as the
+     * foreground service starts. This time limit will only be reset once every 24 hours or if the
+     * app comes into the foreground state.
+     *
      * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
      *                the service started.
      * @param fgsType the {@link ServiceInfo.ForegroundServiceType foreground service type} which
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index c6a8762..342479b 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5062,21 +5062,29 @@
     /**
      * <p>The version of the session configuration query
      * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
-     * API</p>
+     * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+     * APIs.</p>
      * <p>The possible values in this key correspond to the values defined in
      * android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the
      * camera device must reliably report whether they are supported via
-     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
-     * API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
-     * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support
      * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }.
-     * Calling the method for this camera ID throws an UnsupportedOperationException.</p>
-     * <p>If set to VANILLA_ICE_CREAM, the application can call
+     * It also defines the set of session specific keys in CameraCharacteristics when returned from
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }.
+     * The version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
+     * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support the
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup } API.
+     * Trying to create a CameraDeviceSetup instance throws an UnsupportedOperationException.</p>
+     * <p>From VANILLA_ICE_CREAM onwards, the camera compliance tests verify a set of
+     * commonly used SessionConfigurations to ensure that the outputs of
      * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
-     * to check if the combinations of below features are supported.</p>
+     * and {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#getSessionCharacteristics }
+     * are accurate. The application is encouraged to use these SessionConfigurations when turning on
+     * multiple features at the same time.</p>
+     * <p>When set to VANILLA_ICE_CREAM, the combinations of the following configurations are verified
+     * by the compliance tests:</p>
      * <ul>
-     * <li>A subset of LIMITED-level device stream combinations.</li>
-     * </ul>
+     * <li>
+     * <p>A set of commonly used stream combinations:</p>
      * <table>
      * <thead>
      * <tr>
@@ -5084,257 +5092,108 @@
      * <th style="text-align: center;">Size</th>
      * <th style="text-align: center;">Target 2</th>
      * <th style="text-align: center;">Size</th>
-     * <th style="text-align: center;">Sample use case(s)</th>
      * </tr>
      * </thead>
      * <tbody>
      * <tr>
      * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;">Simple preview, GPU video processing, or no-preview video recording.</td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">PREVIEW</td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
      * <td style="text-align: center;">S1080P</td>
      * <td style="text-align: center;"></td>
      * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
      * <td style="text-align: center;">PRIV</td>
      * <td style="text-align: center;">S720P</td>
      * <td style="text-align: center;"></td>
      * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;">In-application video/image processing.</td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">PREVIEW</td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S720P</td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">PREVIEW</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;">Standard still imaging.</td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
      * <td style="text-align: center;">PRIV</td>
      * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;"></td>
+     * <td style="text-align: center;">JPEG/JPEG_R</td>
+     * <td style="text-align: center;">MAXIMUM_16_9</td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">PRIV</td>
+     * <td style="text-align: center;">S1080P</td>
+     * <td style="text-align: center;">JPEG/JPEG_R</td>
+     * <td style="text-align: center;">UHD</td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">PRIV</td>
+     * <td style="text-align: center;">S1080P</td>
+     * <td style="text-align: center;">JPEG/JPEG_R</td>
+     * <td style="text-align: center;">S1440P</td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">PRIV</td>
+     * <td style="text-align: center;">S1080P</td>
+     * <td style="text-align: center;">JPEG/JPEG_R</td>
+     * <td style="text-align: center;">S1080P</td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">PRIV</td>
+     * <td style="text-align: center;">S1080P</td>
+     * <td style="text-align: center;">PRIV</td>
+     * <td style="text-align: center;">UHD</td>
      * </tr>
      * <tr>
      * <td style="text-align: center;">PRIV</td>
      * <td style="text-align: center;">S720P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;"></td>
+     * <td style="text-align: center;">JPEG/JPEG_R</td>
+     * <td style="text-align: center;">MAXIMUM_16_9</td>
      * </tr>
      * <tr>
      * <td style="text-align: center;">PRIV</td>
      * <td style="text-align: center;">S720P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">PREVIEW</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;">In-app processing plus still capture.</td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S720P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">MAXIMUM</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S720P</td>
-     * <td style="text-align: center;">JPEG</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">PREVIEW</td>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">PREVIEW</td>
-     * <td style="text-align: center;">Standard recording.</td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;"></td>
+     * <td style="text-align: center;">JPEG/JPEG_R</td>
+     * <td style="text-align: center;">UHD</td>
      * </tr>
      * <tr>
      * <td style="text-align: center;">PRIV</td>
      * <td style="text-align: center;">S720P</td>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S720P</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">PREVIEW</td>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">PREVIEW</td>
-     * <td style="text-align: center;">Preview plus in-app processing.</td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S1440P</td>
-     * <td style="text-align: center;"></td>
-     * </tr>
-     * <tr>
-     * <td style="text-align: center;">PRIV</td>
+     * <td style="text-align: center;">JPEG/JPEG_R</td>
      * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S1080P</td>
-     * <td style="text-align: center;"></td>
      * </tr>
      * <tr>
      * <td style="text-align: center;">PRIV</td>
-     * <td style="text-align: center;">S720P</td>
-     * <td style="text-align: center;">YUV</td>
-     * <td style="text-align: center;">S720P</td>
-     * <td style="text-align: center;"></td>
+     * <td style="text-align: center;">XVGA</td>
+     * <td style="text-align: center;">JPEG/JPEG_R</td>
+     * <td style="text-align: center;">MAXIMUM_4_3</td>
+     * </tr>
+     * <tr>
+     * <td style="text-align: center;">PRIV</td>
+     * <td style="text-align: center;">S1080P_4_3</td>
+     * <td style="text-align: center;">JPEG/JPEG_R</td>
+     * <td style="text-align: center;">MAXIMUM_4_3</td>
      * </tr>
      * </tbody>
      * </table>
-     * <pre><code>- {@code MAXIMUM} size refers to the camera device's maximum output resolution for
-     *   that format from {@code StreamConfigurationMap#getOutputSizes}. {@code PREVIEW} size
-     *   refers to the best size match to the device's screen resolution, or to 1080p
-     *   (@code 1920x1080}, whichever is smaller. Both sizes are guaranteed to be supported.
-     *
-     * - {@code S1440P} refers to {@code 1920x1440 (4:3)} and {@code 2560x1440 (16:9)}.
-     *   {@code S1080P} refers to {@code 1440x1080 (4:3)} and {@code 1920x1080 (16:9)}.
-     *   And {@code S720P} refers to {@code 960x720 (4:3)} and {@code 1280x720 (16:9)}.
-     *
-     * - If a combination contains a S1440P, S1080P, or S720P stream,
-     *   both 4:3 and 16:9 aspect ratio sizes can be queried. For example, for the
-     *   stream combination of {PRIV, S1440P, JPEG, MAXIMUM}, and if MAXIMUM ==
-     *   4032 x 3024, the application will be able to query both
-     *   {PRIV, 1920 x 1440, JPEG, 4032 x 3024} and {PRIV, 2560 x 1440, JPEG, 4032 x 2268}
-     *   without an exception being thrown.
-     * </code></pre>
      * <ul>
-     * <li>VIDEO_STABILIZATION_MODES: {OFF, PREVIEW}</li>
-     * <li>AE_TARGET_FPS_RANGE: { {<em>, 30}, {</em>, 60} }</li>
-     * <li>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</li>
+     * <li>{@code MAXIMUM_4_3} refers to the camera device's maximum output resolution with
+     *   4:3 aspect ratio for that format from {@code StreamConfigurationMap#getOutputSizes}.</li>
+     * <li>{@code MAXIMUM_16_9} is the maximum output resolution with 16:9 aspect ratio.</li>
+     * <li>{@code S1440P} refers to {@code 2560x1440 (16:9)}.</li>
+     * <li>{@code S1080P} refers to {@code 1920x1080 (16:9)}.</li>
+     * <li>{@code S720P} refers to {@code 1280x720 (16:9)}.</li>
+     * <li>{@code UHD} refers to {@code 3840x2160 (16:9)}.</li>
+     * <li>{@code XVGA} refers to {@code 1024x768 (4:3)}.</li>
+     * <li>{@code S1080P_43} refers to {@code 1440x1080 (4:3)}.</li>
      * </ul>
+     * </li>
+     * <li>
+     * <p>VIDEO_STABILIZATION_MODE: {OFF, PREVIEW}</p>
+     * </li>
+     * <li>
+     * <p>AE_TARGET_FPS_RANGE: { {*, 30}, {*, 60} }</p>
+     * </li>
+     * <li>
+     * <p>DYNAMIC_RANGE_PROFILE: {STANDARD, HLG10}</p>
+     * </li>
+     * </ul>
+     * <p>All of the above configurations can be set up with a SessionConfiguration. The list of
+     * OutputConfiguration contains the stream configurations and DYNAMIC_RANGE_PROFILE, and
+     * the AE_TARGET_FPS_RANGE and VIDEO_STABILIZATION_MODE are set as session parameters.</p>
      * <p>This key is available on all devices.</p>
      */
     @PublicKey
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index eb644e8..dfbf06b 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -917,8 +917,11 @@
      * image. For example, it can be used as a temporary placeholder for the requested capture
      * while the final image is being processed. The supported sizes for a still capture's postview
      * can be retrieved using
-     * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.
-     * The formats of the still capture and postview should be equivalent upon capture request.</p>
+     * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.</p>
+     *
+     * <p>Starting with Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
+     * the formats of the still capture and postview are not required to be equivalent upon capture
+     * request.</p>
      *
      * @param extension the extension type
      * @return {@code true} in case postview is supported, {@code false} otherwise
@@ -976,8 +979,7 @@
      *
      * @param extension the extension type
      * @param captureSize size of the still capture for which the postview is requested
-     * @param format device-specific extension output format of the still capture and
-     * postview
+     * @param format device-specific extension output format of the postview
      * @return non-modifiable list of available sizes or an empty list if the format and
      * size is not supported.
      * @throws IllegalArgumentException in case of unsupported extension or if postview
@@ -1018,8 +1020,8 @@
                 }
                 IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
                 extender.init(mCameraId, mCharacteristicsMapNative);
-                return generateSupportedSizes(extender.getSupportedPostviewResolutions(
-                    sz), format, streamMap);
+                return getSupportedSizes(extender.getSupportedPostviewResolutions(sz),
+                        format);
             } else {
                 Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
                         initializeExtension(extension);
@@ -1034,15 +1036,13 @@
                 }
 
                 if (format == ImageFormat.YUV_420_888) {
-                    return generateSupportedSizes(
-                            extenders.second.getSupportedPostviewResolutions(sz),
-                            format, streamMap);
+                    return getSupportedSizes(
+                            extenders.second.getSupportedPostviewResolutions(sz), format);
                 } else if (format == ImageFormat.JPEG) {
                     // The framework will perform the additional encoding pass on the
                     // processed YUV_420 buffers.
-                    return generateJpegSupportedSizes(
-                            extenders.second.getSupportedPostviewResolutions(sz),
-                                    streamMap);
+                    return getSupportedSizes(
+                            extenders.second.getSupportedPostviewResolutions(sz), format);
                 }  else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
                     // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
                     // extension case
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index 875550a..a10e250 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -36,6 +36,8 @@
 import android.util.Log;
 import android.view.Surface;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.nio.ByteBuffer;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -57,6 +59,8 @@
     private android.hardware.camera2.extension.Size mResolution = null;
     private android.hardware.camera2.extension.Size mPostviewResolution = null;
     private int mFormat = -1;
+    private int mPostviewFormat = -1;
+    private int mCaptureFormat = -1;
     private Surface mOutputSurface = null;
     private ImageWriter mOutputWriter = null;
     private Surface mPostviewOutputSurface = null;
@@ -204,10 +208,12 @@
     }
 
     public void onOutputSurface(Surface surface, int format) throws RemoteException {
-        if (format != ImageFormat.JPEG) {
+        if (!Flags.extension10Bit() && format != ImageFormat.JPEG) {
             Log.e(TAG, "Unsupported output format: " + format);
             return;
         }
+        CameraExtensionUtils.SurfaceInfo surfaceInfo = CameraExtensionUtils.querySurface(surface);
+        mCaptureFormat = surfaceInfo.mFormat;
         mOutputSurface = surface;
         initializePipeline();
     }
@@ -215,10 +221,11 @@
     public void onPostviewOutputSurface(Surface surface) throws RemoteException {
         CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
                 CameraExtensionUtils.querySurface(surface);
-        if (postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
+        if (!Flags.extension10Bit() && postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
             Log.e(TAG, "Unsupported output format: " + postviewSurfaceInfo.mFormat);
             return;
         }
+        mPostviewFormat = postviewSurfaceInfo.mFormat;
         mPostviewOutputSurface = surface;
         initializePostviewPipeline();
     }
@@ -233,7 +240,7 @@
     }
 
     public void onImageFormatUpdate(int format) throws RemoteException {
-        if (format != ImageFormat.YUV_420_888) {
+        if (!Flags.extension10Bit() && format != ImageFormat.YUV_420_888) {
             Log.e(TAG, "Unsupported input format: " + format);
             return;
         }
@@ -244,33 +251,45 @@
     private void initializePipeline() throws RemoteException {
         if ((mFormat != -1) && (mOutputSurface != null) && (mResolution != null) &&
                 (mYuvReader == null)) {
-            // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
-            mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
-                    ImageFormat.JPEG,
-                    (mResolution.width * mResolution.height * 3)/2 + JPEG_APP_SEGMENT_SIZE, 1);
-            mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height, mFormat,
-                    JPEG_QUEUE_SIZE);
-            mYuvReader.setOnImageAvailableListener(
-                    new YuvCallback(mYuvReader, mOutputWriter), mHandler);
-            mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+            if (Flags.extension10Bit() && mCaptureFormat == ImageFormat.YUV_420_888) {
+                // For the case when postview is JPEG and capture is YUV
+                mProcessor.onOutputSurface(mOutputSurface, mCaptureFormat);
+            } else {
+                // Jpeg/blobs are expected to be configured with (w*h)x1.5 + 64k Jpeg APP1 segment
+                mOutputWriter = ImageWriter.newInstance(mOutputSurface, 1 /*maxImages*/,
+                        ImageFormat.JPEG,
+                        (mResolution.width * mResolution.height * 3) / 2
+                        + JPEG_APP_SEGMENT_SIZE, 1);
+                mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height,
+                        mFormat, JPEG_QUEUE_SIZE);
+                mYuvReader.setOnImageAvailableListener(
+                        new YuvCallback(mYuvReader, mOutputWriter), mHandler);
+                mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
+            }
             mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
-            mProcessor.onImageFormatUpdate(mFormat);
+            mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
         }
     }
 
     private void initializePostviewPipeline() throws RemoteException {
         if ((mFormat != -1) && (mPostviewOutputSurface != null) && (mPostviewResolution != null)
                 && (mPostviewYuvReader == null)) {
-            // Jpeg/blobs are expected to be configured with (w*h)x1
-            mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface, 1/*maxImages*/,
-                    ImageFormat.JPEG, mPostviewResolution.width * mPostviewResolution.height, 1);
-            mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
-                    mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
-            mPostviewYuvReader.setOnImageAvailableListener(
-                    new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
-            mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+            if (Flags.extension10Bit() && mPostviewFormat == ImageFormat.YUV_420_888) {
+                // For the case when postview is YUV and capture is JPEG
+                mProcessor.onPostviewOutputSurface(mPostviewOutputSurface);
+            } else {
+                // Jpeg/blobs are expected to be configured with (w*h)x1
+                mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface,
+                        1/*maxImages*/, ImageFormat.JPEG,
+                        mPostviewResolution.width * mPostviewResolution.height, 1);
+                mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
+                        mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
+                mPostviewYuvReader.setOnImageAvailableListener(
+                        new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
+                mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+            }
             mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
-            mProcessor.onImageFormatUpdate(mFormat);
+            mProcessor.onImageFormatUpdate(ImageFormat.YUV_420_888);
         }
     }
 
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index c00e610..3ae3199 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -390,7 +390,16 @@
                 if (surfaceInfo.mFormat == ImageFormat.JPEG) {
                     mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
                     mImageProcessor = mImageJpegProcessor;
+                } else if (Flags.extension10Bit() && mClientPostviewSurface != null) {
+                    // Handles case when postview is JPEG and capture is YUV
+                    CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
+                            CameraExtensionUtils.querySurface(mClientPostviewSurface);
+                    if (postviewSurfaceInfo.mFormat == ImageFormat.JPEG) {
+                        mImageJpegProcessor = new CameraExtensionJpegProcessor(mImageProcessor);
+                        mImageProcessor = mImageJpegProcessor;
+                    }
                 }
+
                 mBurstCaptureImageReader = ImageReader.newInstance(surfaceInfo.mWidth,
                         surfaceInfo.mHeight, CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
                         mImageExtender.getMaxCaptureStage());
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index f0c6e2e..40f0477 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -112,19 +112,30 @@
         if (outputConfig == null) return null;
 
         SurfaceInfo surfaceInfo = querySurface(outputConfig.getSurface());
-        if (surfaceInfo.mFormat == captureFormat) {
-            if (supportedPostviewSizes.containsKey(captureFormat)) {
-                Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
-                if (supportedPostviewSizes.get(surfaceInfo.mFormat)
-                        .contains(postviewSize)) {
-                    return outputConfig.getSurface();
-                } else {
-                    throw new IllegalArgumentException("Postview size not supported!");
-                }
+
+        if (Flags.extension10Bit()) {
+            Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+            if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+                    .contains(postviewSize)) {
+                return outputConfig.getSurface();
+            } else {
+                throw new IllegalArgumentException("Postview size not supported!");
             }
         } else {
-            throw new IllegalArgumentException("Postview format should be equivalent to " +
-                    " the capture format!");
+            if (surfaceInfo.mFormat == captureFormat) {
+                if (supportedPostviewSizes.containsKey(captureFormat)) {
+                    Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+                    if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+                            .contains(postviewSize)) {
+                        return outputConfig.getSurface();
+                    } else {
+                        throw new IllegalArgumentException("Postview size not supported!");
+                    }
+                }
+            } else {
+                throw new IllegalArgumentException("Postview format should be equivalent to "
+                        + " the capture format!");
+            }
         }
 
         return null;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 72ab970..e6ddf35 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1736,6 +1736,24 @@
             "android.settings.NETWORK_OPERATOR_SETTINGS";
 
     /**
+     * Activity Action: Show settings for selecting the network provider.
+     * <p>
+     * In some cases, a matching Activity may not be provided, so ensure you
+     * safeguard against this.
+     * <p>
+     * Access to this preference can be customized via Settings' app.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_NETWORK_PROVIDER_SETTINGS =
+            "android.settings.NETWORK_PROVIDER_SETTINGS";
+
+    /**
      * Activity Action: Show settings for selection of 2G/3G.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 997c9581..5f6bdbf 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1383,16 +1383,22 @@
                 DreamService.DREAM_META_DATA, DREAM_META_DATA_ROOT_TAG,
                 com.android.internal.R.styleable.Dream)) {
             if (rawMetadata == null) return null;
-            return new DreamMetadata(
-                    convertToComponentName(
-                    rawMetadata.getString(
-                            com.android.internal.R.styleable.Dream_settingsActivity), serviceInfo),
-                    rawMetadata.getDrawable(
-                            com.android.internal.R.styleable.Dream_previewImage),
-                    rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
-                            DEFAULT_SHOW_COMPLICATIONS),
-                    rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
-                    );
+            try {
+                return new DreamMetadata(
+                        convertToComponentName(
+                                rawMetadata.getString(
+                                        com.android.internal.R.styleable.Dream_settingsActivity),
+                                serviceInfo),
+                        rawMetadata.getDrawable(
+                                com.android.internal.R.styleable.Dream_previewImage),
+                        rawMetadata.getBoolean(R.styleable.Dream_showClockAndComplications,
+                                DEFAULT_SHOW_COMPLICATIONS),
+                        rawMetadata.getInt(R.styleable.Dream_dreamCategory, DREAM_CATEGORY_DEFAULT)
+                );
+            } catch (Exception exception) {
+                Log.e(TAG, "Failed to create read metadata", exception);
+                return null;
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c7ca16b..f0d27da 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4276,6 +4276,10 @@
             mPreferredFrameRate = -1;
             mIsFrameRateConflicted = false;
             mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
+        } else if (mPreferredFrameRate == 0) {
+            // From MSG_FRAME_RATE_SETTING, where mPreferredFrameRate is set to 0
+            setPreferredFrameRate(0);
+            mPreferredFrameRate = -1;
         }
     }
 
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 7c7c7b8..9f9aae5 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -473,7 +473,14 @@
         } finally {
             // Unconditionally skip to the end of the written data, even if the actual parcel
             // format is incompatible
-            parcel.setDataPosition(endPos);
+            if (endPos > parcel.dataPosition()) {
+                if (endPos >= parcel.dataSize()) {
+                    throw new IndexOutOfBoundsException(
+                            "PowerStats end position: " + endPos + " is outside the parcel bounds: "
+                                    + parcel.dataSize());
+                }
+                parcel.setDataPosition(endPos);
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 69d3d6a..c21a43e 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -53,8 +53,6 @@
 
     public ScreenshotHelper(Context context) {
         mContext = context;
-        IntentFilter filter = new IntentFilter(ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
     }
 
     /**
@@ -108,6 +106,8 @@
     public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
             @Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
         synchronized (mScreenshotLock) {
+            mContext.registerReceiver(mBroadcastReceiver,
+                new IntentFilter(ACTION_USER_SWITCHED), Context.RECEIVER_EXPORTED);
 
             final Runnable mScreenshotTimeout = () -> {
                 synchronized (mScreenshotLock) {
@@ -223,6 +223,11 @@
             mScreenshotConnection = null;
             mScreenshotService = null;
         }
+        try {
+            mContext.unregisterReceiver(mBroadcastReceiver);
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "Attempted to remove broadcast receiver twice");
+        }
     }
 
     /**
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index f885e31..32aec1a 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -36,6 +36,7 @@
 
 import android.annotation.NonNull;
 import android.app.Activity;
+import android.app.Instrumentation;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
@@ -47,6 +48,7 @@
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -537,6 +539,28 @@
         });
         waitForAfterDraw();
     }
+
+    @Test
+    @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+            FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
+    })
+    public void frameRateReset() throws Throwable {
+        mMovingView.setRequestedFrameRate(120f);
+        waitForFrameRateCategoryToSettle();
+        mActivityRule.runOnUiThread(() -> mMovingView.setVisibility(View.INVISIBLE));
+
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        for (int i = 0; i < 120; i++) {
+            mActivityRule.runOnUiThread(() -> {
+                mMovingView.getParent().onDescendantInvalidated(mMovingView, mMovingView);
+            });
+            instrumentation.waitForIdleSync();
+        }
+
+        assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f);
+    }
+
     private void runAfterDraw(@NonNull Runnable runnable) {
         Handler handler = new Handler(Looper.getMainLooper());
         mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index 6402206..baab3b2 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -168,6 +168,20 @@
         assertThat(end).isEqualTo("END");
     }
 
+    @Test
+    public void parceling_corruptParcel() {
+        PowerStats stats = new PowerStats(mDescriptor);
+        Parcel parcel = Parcel.obtain();
+        stats.writeToParcel(parcel);
+
+        Parcel newParcel = marshallAndUnmarshall(parcel);
+        newParcel.writeInt(-42);        // Negative section length
+        newParcel.setDataPosition(0);
+
+        PowerStats newStats = PowerStats.readFromParcel(newParcel, mRegistry);
+        assertThat(newStats).isNull();
+    }
+
     private static Parcel marshallAndUnmarshall(Parcel parcel) {
         byte[] bytes = parcel.marshall();
         parcel.recycle();
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index e1bf40c..6110133 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -105,18 +105,21 @@
     }
 
     @Test
-    fun onDragUpdate_stayOnSameSide() {
+    fun drag_stayOnSameSide() {
         runOnMainSync {
             controller.onDragStart(initialLocationOnLeft = false)
             controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+            controller.onDragEnd()
         }
         waitForAnimateIn()
         assertThat(dropTargetView).isNull()
         assertThat(testListener.locationChanges).isEmpty()
+        assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
     }
 
     @Test
-    fun onDragUpdate_toLeft() {
+    fun drag_toLeft() {
+        // Drag to left, but don't finish
         runOnMainSync {
             controller.onDragStart(initialLocationOnLeft = false)
             controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -132,10 +135,16 @@
             .isEqualTo(expectedDropTargetBounds.height())
 
         assertThat(testListener.locationChanges).containsExactly(BubbleBarLocation.LEFT)
+        assertThat(testListener.locationReleases).isEmpty()
+
+        // Finish the drag
+        runOnMainSync { controller.onDragEnd() }
+        assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.LEFT)
     }
 
     @Test
-    fun onDragUpdate_toLeftAndBackToRight() {
+    fun drag_toLeftAndBackToRight() {
+        // Drag to left
         runOnMainSync {
             controller.onDragStart(initialLocationOnLeft = false)
             controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
@@ -143,6 +152,7 @@
         waitForAnimateIn()
         assertThat(dropTargetView).isNotNull()
 
+        // Drag to right
         runOnMainSync { controller.onDragUpdate(pointOnRight.x, pointOnRight.y) }
         // We have to wait for existing drop target to animate out and new to animate in
         waitForAnimateOut()
@@ -158,10 +168,15 @@
 
         assertThat(testListener.locationChanges)
             .containsExactly(BubbleBarLocation.LEFT, BubbleBarLocation.RIGHT)
+        assertThat(testListener.locationReleases).isEmpty()
+
+        // Release the view
+        runOnMainSync { controller.onDragEnd() }
+        assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
     }
 
     @Test
-    fun onDragUpdate_toLeftInExclusionRect() {
+    fun drag_toLeftInExclusionRect() {
         runOnMainSync {
             controller.onDragStart(initialLocationOnLeft = false)
             // Exclusion rect is around the bottom center area of the screen
@@ -170,6 +185,10 @@
         waitForAnimateIn()
         assertThat(dropTargetView).isNull()
         assertThat(testListener.locationChanges).isEmpty()
+        assertThat(testListener.locationReleases).isEmpty()
+
+        runOnMainSync { controller.onDragEnd() }
+        assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
     }
 
     @Test
@@ -256,8 +275,13 @@
 
     internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
         val locationChanges = mutableListOf<BubbleBarLocation>()
+        val locationReleases = mutableListOf<BubbleBarLocation>()
         override fun onChange(location: BubbleBarLocation) {
             locationChanges.add(location)
         }
+
+        override fun onRelease(location: BubbleBarLocation) {
+            locationReleases.add(location)
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d295877..98b2431 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -725,6 +725,17 @@
         }
     }
 
+    /**
+     * Animate bubble bar to the given location. The location change is transient. It does not
+     * update the state of the bubble bar.
+     * To update bubble bar pinned location, use {@link #setBubbleBarLocation(BubbleBarLocation)}.
+     */
+    public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+        if (canShowAsBubbleBar()) {
+            mBubbleStateListener.animateBubbleBarLocation(bubbleBarLocation);
+        }
+    }
+
     /** Whether this userId belongs to the current user. */
     private boolean isCurrentProfile(int userId) {
         return userId == UserHandle.USER_ALL
@@ -2250,15 +2261,19 @@
         private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
         private final Bubbles.BubbleStateListener mBubbleListener =
                 new Bubbles.BubbleStateListener() {
+                    @Override
+                    public void onBubbleStateChange(BubbleBarUpdate update) {
+                        Bundle b = new Bundle();
+                        b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+                        b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
+                        mListener.call(l -> l.onBubbleStateChange(b));
+                    }
 
-            @Override
-            public void onBubbleStateChange(BubbleBarUpdate update) {
-                Bundle b = new Bundle();
-                b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
-                b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
-                mListener.call(l -> l.onBubbleStateChange(b));
-            }
-        };
+                    @Override
+                    public void animateBubbleBarLocation(BubbleBarLocation location) {
+                        mListener.call(l -> l.animateBubbleBarLocation(location));
+                    }
+                };
 
         IBubblesImpl(BubbleController controller) {
             mController = controller;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 127a49f..322088b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -37,6 +37,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 
@@ -304,6 +305,12 @@
          * Called when the bubbles state changes.
          */
         void onBubbleStateChange(BubbleBarUpdate update);
+
+        /**
+         * Called when bubble bar should temporarily be animated to a new location.
+         * Does not result in a state change.
+         */
+        void animateBubbleBarLocation(BubbleBarLocation location);
     }
 
     /** Listener to find out about stack expansion / collapse events. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
index e48f8d5..14d29cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -15,8 +15,9 @@
  */
 
 package com.android.wm.shell.bubbles;
-import android.os.Bundle;
 
+import android.os.Bundle;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 /**
  * Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
  */
@@ -26,4 +27,10 @@
      * Called when the bubbles state changes.
      */
     void onBubbleStateChange(in Bundle update);
+
+    /**
+     * Called when bubble bar should temporarily be animated to a new location.
+     * Does not result in a state change.
+     */
+    void animateBubbleBarLocation(in BubbleBarLocation location);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index fe9c4d4..a51ac63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -135,9 +135,9 @@
 
         private fun finishDrag() {
             if (!isStuckToDismiss) {
-                animationHelper.animateToRestPosition()
                 pinController.onDragEnd()
                 dragListener.onReleased(inDismiss = false)
+                animationHelper.animateToRestPosition()
                 dismissView.hide()
             }
             isMoving = false
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 62cc4da..a351cef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -33,6 +33,8 @@
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
+import androidx.annotation.NonNull;
+
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.BubbleData;
@@ -42,6 +44,8 @@
 import com.android.wm.shell.bubbles.DeviceConfig;
 import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
+import com.android.wm.shell.common.bubbles.BaseBubblePinController;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 import com.android.wm.shell.common.bubbles.DismissView;
 
 import kotlin.Unit;
@@ -115,7 +119,18 @@
 
         mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
                 context, this, mPositioner);
-        mBubbleExpandedViewPinController.setListener(mBubbleController::setBubbleBarLocation);
+        mBubbleExpandedViewPinController.setListener(
+                new BaseBubblePinController.LocationChangeListener() {
+                    @Override
+                    public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
+                        mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
+                    }
+
+                    @Override
+                    public void onRelease(@NonNull BubbleBarLocation location) {
+                        mBubbleController.setBubbleBarLocation(location);
+                    }
+                });
 
         setOnClickListener(view -> hideMenuOrCollapse());
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
index a008045..e514f9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
@@ -82,6 +82,7 @@
     fun onDragEnd() {
         getDropTargetView()?.let { view -> view.animateOut { removeDropTargetView(view) } }
         dismissZone = null
+        listener?.onRelease(if (onLeft) LEFT else RIGHT)
     }
 
     /**
@@ -170,14 +171,22 @@
     /** Receive updates on location changes */
     interface LocationChangeListener {
         /**
-         * Bubble bar [BubbleBarLocation] has changed as a result of dragging
+         * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in
+         * progress.
          *
          * Triggered when drag gesture passes the middle of the screen and before touch up. Can be
          * triggered multiple times per gesture.
          *
          * @param location new location as a result of the ongoing drag operation
          */
-        fun onChange(location: BubbleBarLocation)
+        fun onChange(location: BubbleBarLocation) {}
+
+        /**
+         * Bubble bar has been released in the [BubbleBarLocation].
+         *
+         * @param location final location of the bubble bar once drag is released
+         */
+        fun onRelease(location: BubbleBarLocation)
     }
 
     companion object {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
index 64dcf6e..395354e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -34,6 +34,7 @@
         "compatibility-device-util-axt",
         "platform-test-annotations",
         "truth",
+        "uiautomator-helpers",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 0ab99fa..66943d4 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -30,6 +30,8 @@
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Instrumentation;
 import android.app.KeyguardManager;
@@ -43,6 +45,8 @@
 import android.hardware.display.DisplayManager;
 import android.media.AudioManager;
 import android.os.PowerManager;
+import android.os.RemoteException;
+import android.platform.uiautomator_helpers.WaitUtils;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.Display;
@@ -51,6 +55,8 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.Configurator;
+import androidx.test.uiautomator.UiDevice;
 
 import com.android.compatibility.common.util.TestUtils;
 import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
@@ -60,7 +66,6 @@
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -76,11 +81,13 @@
     private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
     private static final int TIMEOUT_UI_CHANGE_S = 5;
     private static final int NO_GLOBAL_ACTION = -1;
-    private static final Intent INTENT_OPEN_MENU = new Intent(INTENT_TOGGLE_MENU)
-            .setPackage(PACKAGE_NAME);
+    private static final Intent INTENT_OPEN_MENU =
+            new Intent(INTENT_TOGGLE_MENU).setPackage(PACKAGE_NAME);
+    private static final String SERVICE_NAME = PACKAGE_NAME + "/.AccessibilityMenuService";
 
     private static Instrumentation sInstrumentation;
     private static UiAutomation sUiAutomation;
+    private static UiDevice sUiDevice;
     private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
     private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
 
@@ -91,12 +98,14 @@
 
     @BeforeClass
     public static void classSetup() throws Throwable {
-        final String serviceName = PACKAGE_NAME + "/.AccessibilityMenuService";
+        Configurator.getInstance()
+                .setUiAutomationFlags(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
         sUiAutomation = sInstrumentation.getUiAutomation(
                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         sUiAutomation.adoptShellPermissionIdentity(
                 UiAutomation.ALL_PERMISSIONS.toArray(new String[0]));
+        sUiDevice = UiDevice.getInstance(sInstrumentation);
 
         final Context context = sInstrumentation.getTargetContext();
         sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -117,13 +126,13 @@
 
         // Enable a11yMenu service.
         Settings.Secure.putString(context.getContentResolver(),
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, serviceName);
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, SERVICE_NAME);
 
         TestUtils.waitUntil("Failed to enable service",
                 TIMEOUT_SERVICE_STATUS_CHANGE_S,
                 () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
-                                info -> info.getId().contains(serviceName)).count() == 1);
+                                info -> info.getId().contains(SERVICE_NAME)).count() == 1);
         context.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -159,8 +168,11 @@
     public void tearDown() throws Throwable {
         closeMenu();
         sLastGlobalAction.set(NO_GLOBAL_ACTION);
+        // Leave the device in clean state when the test finished
+        unlockSignal();
         // dismisses screenshot popup if present.
-        sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+        sUiDevice.pressBack();
+        sUiDevice.pressHome();
     }
 
     private static boolean isMenuVisible() {
@@ -168,38 +180,25 @@
         return root != null && root.getPackageName().toString().equals(PACKAGE_NAME);
     }
 
-    private static void wakeUpScreen() throws Throwable {
-        sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        TestUtils.waitUntil("Screen did not wake up.",
-                TIMEOUT_UI_CHANGE_S,
-                () -> sPowerManager.isInteractive());
+    private static void wakeUpScreen() throws RemoteException {
+        sUiDevice.wakeUp();
+        WaitUtils.waitForValueToSettle("Screen On", AccessibilityMenuServiceTest::isScreenOn);
+        assertWithMessage("Screen is on").that(isScreenOn()).isTrue();
     }
 
     private static void closeScreen() throws Throwable {
-        Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
         sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
-        TestUtils.waitUntil("Screen did not close.",
-                TIMEOUT_UI_CHANGE_S,
-                () -> !sPowerManager.isInteractive()
-                        && display.getState() == Display.STATE_OFF
-        );
+        WaitUtils.waitForValueToSettle("Screen Off", AccessibilityMenuServiceTest::isScreenOff);
+        assertWithMessage("Screen is off").that(isScreenOff()).isTrue();
     }
 
     private static void openMenu() throws Throwable {
         unlockSignal();
-        sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
-
-        TestUtils.waitUntil("Timed out before menu could appear.",
-                TIMEOUT_UI_CHANGE_S,
-                () -> {
-                    if (isMenuVisible()) {
-                        return true;
-                    } else {
-                        unlockSignal();
-                        sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
-                        return false;
-                    }
-                });
+        if (!isMenuVisible()) {
+            sInstrumentation.getTargetContext().sendBroadcast(INTENT_OPEN_MENU);
+            sUiDevice.waitForIdle();
+            WaitUtils.ensureThat("Accessibility Menu is visible", () -> isMenuVisible());
+        }
     }
 
     private static void closeMenu() throws Throwable {
@@ -342,7 +341,9 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assistantButton.performAction(CLICK_ID),
-                (event) -> expectedPackage.contains(event.getPackageName()),
+                (event) ->
+                        event.getPackageName() != null
+                                && expectedPackage.contains(event.getPackageName()),
                 TIMEOUT_UI_CHANGE_S * 1000
         );
     }
@@ -358,7 +359,9 @@
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> settingsButton.performAction(CLICK_ID),
-                (event) -> expectedPackage.contains(event.getPackageName()),
+                (event) ->
+                        event.getPackageName() != null
+                                && expectedPackage.contains(event.getPackageName()),
                 TIMEOUT_UI_CHANGE_S * 1000
         );
     }
@@ -454,24 +457,40 @@
     }
 
     @Test
-    @Ignore("Test failure in pre/postsubmit cannot be replicated on local devices. "
-            + "Coverage is low-impact.")
     public void testOnScreenLock_cannotOpenMenu() throws Throwable {
         closeScreen();
         wakeUpScreen();
+        sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
+        sUiDevice.waitForIdle();
 
         TestUtils.waitUntil("Did not receive signal that menu cannot open",
                 TIMEOUT_UI_CHANGE_S,
-                () -> {
-                    sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU);
-                    return sOpenBlocked.get();
-                });
+                sOpenBlocked::get);
     }
 
-    private static void unlockSignal() {
-        // MENU unlocks screen,
-        // BACK closes any menu that may appear if the screen wasn't locked.
-        sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU");
-        sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
+    private static void unlockSignal() throws RemoteException {
+        if (!sKeyguardManager.isKeyguardLocked()) {
+            return;
+        }
+        // go/adb-cheats#unlock-screen
+        wakeUpScreen();
+        if (sKeyguardManager.isKeyguardLocked()) {
+            sUiDevice.pressMenu();
+        }
+        WaitUtils.ensureThat(
+                "Device unlocked & isInteractive",
+                () -> isScreenOn() && !sKeyguardManager.isKeyguardLocked());
+    }
+
+    private static boolean isScreenOn() {
+        int display = Display.DEFAULT_DISPLAY;
+        return sPowerManager.isInteractive(display)
+                && sDisplayManager.getDisplay(display).getState() == Display.STATE_ON;
+    }
+
+    private static boolean isScreenOff() {
+        int display = Display.DEFAULT_DISPLAY;
+        return !sPowerManager.isInteractive(display)
+                && sDisplayManager.getDisplay(display).getState() == Display.STATE_OFF;
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 338987a..7d56a67 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,6 +20,8 @@
 import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.util.SizeF
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
 import android.widget.FrameLayout
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.animateFloatAsState
@@ -115,8 +117,6 @@
 import androidx.compose.ui.window.Popup
 import androidx.core.view.setPadding
 import androidx.window.layout.WindowMetricsCalculator
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.padding
 import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -300,7 +300,7 @@
                             viewModel.onHidePopup()
                             viewModel.onOpenWidgetEditor(selectedKey.value)
                         },
-                        onHide = { viewModel.onHidePopup()}
+                        onHide = { viewModel.onHidePopup() }
                     )
                 }
                 null -> {}
@@ -374,7 +374,7 @@
             liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
 
         // Scroll if current position is behind the first updated content
-        if (indexOfFirstUpdatedContent in 0..<gridState.firstVisibleItemIndex) {
+        if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
             // Launching with a scope to prevent the job from being canceled in the case of a
             // recomposition during scrolling
             coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
@@ -841,6 +841,8 @@
     widgetConfigurator: WidgetConfigurator?,
     modifier: Modifier = Modifier,
 ) {
+    val isFocusable by viewModel.isFocusable.collectAsState(initial = false)
+
     Box(
         modifier =
             modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
@@ -865,6 +867,16 @@
                         setPadding(0)
                     }
             },
+            update = {
+                it.apply {
+                    importantForAccessibility =
+                        if (isFocusable) {
+                            IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                        } else {
+                            IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                        }
+                }
+            },
             // For reusing composition in lazy lists.
             onReset = {},
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
index f354b80..74af3ca 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt
@@ -98,10 +98,10 @@
     }
 
     override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
-        return onClick != null || super.onInterceptTouchEvent(ev)
+        return (isSliceViewClickable && onClick != null) || super.onInterceptTouchEvent(ev)
     }
 
     override fun onClick(v: View?) {
-        onClick?.let { it() } ?: super.onClick(v)
+        onClick?.takeIf { isSliceViewClickable }?.let { it() } ?: super.onClick(v)
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 4f3a6c8..874c0a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -38,6 +38,8 @@
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.state.ToggleableState
 import androidx.compose.ui.unit.dp
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
@@ -79,6 +81,12 @@
                     modifier =
                         Modifier.fillMaxSize().padding(8.dp).semantics {
                             role = Role.Switch
+                            toggleableState =
+                                if (viewModel.isActive) {
+                                    ToggleableState.On
+                                } else {
+                                    ToggleableState.Off
+                                }
                             contentDescription = label
                         },
                     onClick = { onCheckedChange(!viewModel.isActive) },
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 c96a8ce..9e9a002 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
@@ -24,17 +24,21 @@
 import android.provider.Settings
 import android.widget.RemoteViews
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
 import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 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.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
@@ -45,8 +49,14 @@
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
+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.log.logcatLogBuffer
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -63,6 +73,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -94,6 +105,8 @@
     private lateinit var mediaRepository: FakeCommunalMediaRepository
     private lateinit var userRepository: FakeUserRepository
     private lateinit var shadeTestUtil: ShadeTestUtil
+    private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var communalRepository: FakeCommunalRepository
 
     private lateinit var underTest: CommunalViewModel
 
@@ -106,12 +119,14 @@
         MockitoAnnotations.initMocks(this)
 
         keyguardRepository = kosmos.fakeKeyguardRepository
+        keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
         tutorialRepository = kosmos.fakeCommunalTutorialRepository
         widgetRepository = kosmos.fakeCommunalWidgetRepository
         smartspaceRepository = kosmos.fakeSmartspaceRepository
         mediaRepository = kosmos.fakeCommunalMediaRepository
         userRepository = kosmos.fakeUserRepository
         shadeTestUtil = kosmos.shadeTestUtil
+        communalRepository = kosmos.fakeCommunalRepository
 
         kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
@@ -125,6 +140,7 @@
         underTest =
             CommunalViewModel(
                 testScope,
+                kosmos.keyguardTransitionInteractor,
                 kosmos.communalInteractor,
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
@@ -326,6 +342,105 @@
             assertThat(underTest.canChangeScene()).isFalse()
         }
 
+    @Test
+    fun isFocusable_isFalse_whenTransitioningAwayFromGlanceableHub() =
+        testScope.runTest {
+            val isFocusable by collectLastValue(underTest.isFocusable)
+
+            // Shade not expanded.
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
+            // On communal scene.
+            communalRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+            )
+            // Open bouncer.
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+
+            keyguardTransitionRepository.sendTransitionStep(
+                from = KeyguardState.GLANCEABLE_HUB,
+                to = KeyguardState.PRIMARY_BOUNCER,
+                transitionState = TransitionState.RUNNING,
+                value = 0.5f,
+            )
+            assertThat(isFocusable).isEqualTo(false)
+
+            // Transitioned to bouncer.
+            keyguardTransitionRepository.sendTransitionStep(
+                from = KeyguardState.GLANCEABLE_HUB,
+                to = KeyguardState.PRIMARY_BOUNCER,
+                transitionState = TransitionState.FINISHED,
+                value = 1f,
+            )
+            assertThat(isFocusable).isEqualTo(false)
+        }
+
+    @Test
+    fun isFocusable_isFalse_whenNotOnCommunalScene() =
+        testScope.runTest {
+            val isFocusable by collectLastValue(underTest.isFocusable)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = testScope,
+            )
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
+            // Transitioned away from communal scene.
+            communalRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Blank))
+            )
+
+            assertThat(isFocusable).isEqualTo(false)
+        }
+
+    @Test
+    fun isFocusable_isTrue_whenIdleOnCommunal_andShadeNotExpanded() =
+        testScope.runTest {
+            val isFocusable by collectLastValue(underTest.isFocusable)
+
+            // On communal scene.
+            communalRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+            )
+            // Transitioned to Glanceable hub.
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = testScope,
+            )
+            // Shade not expanded.
+            shadeTestUtil.setLockscreenShadeExpansion(0f)
+
+            assertThat(isFocusable).isEqualTo(true)
+        }
+
+    @Test
+    fun isFocusable_isFalse_whenQsIsExpanded() =
+        testScope.runTest {
+            val isFocusable by collectLastValue(underTest.isFocusable)
+
+            // On communal scene.
+            communalRepository.setTransitionState(
+                flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+            )
+            // Transitioned to Glanceable hub.
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GLANCEABLE_HUB,
+                testScope = testScope,
+            )
+            // Qs is expanded.
+            shadeTestUtil.setQsExpansion(1f)
+
+            assertThat(isFocusable).isEqualTo(false)
+        }
+
     private suspend fun setIsMainUser(isMainUser: Boolean) {
         whenever(user.isMain).thenReturn(isMainUser)
         userRepository.setUserInfos(listOf(user))
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 511bdc4..a081ecc 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
@@ -37,8 +37,8 @@
 ) {
     val currentScene: Flow<SceneKey> = communalInteractor.desiredScene
 
-    /** Whether communal hub can be focused to enable accessibility actions. */
-    val isFocusable: Flow<Boolean> = communalInteractor.isIdleOnCommunal
+    /** Whether communal hub can be focused by accessibility tools. */
+    open val isFocusable: Flow<Boolean> = MutableStateFlow(false)
 
     /** Whether widgets are currently being re-ordered. */
     open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
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 9dacf8c..24ea7b6 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
@@ -22,6 +22,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
@@ -54,6 +56,7 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     private val shadeInteractor: ShadeInteractor,
@@ -93,6 +96,18 @@
     private val _currentPopup: MutableStateFlow<PopupType?> = MutableStateFlow(null)
     override val currentPopup: Flow<PopupType?> = _currentPopup.asStateFlow()
 
+    // The widget is focusable for accessibility when the hub is fully visible and shade is not
+    // opened.
+    override val isFocusable: Flow<Boolean> =
+        combine(
+                keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+                communalInteractor.isIdleOnCommunal,
+                shadeInteractor.isAnyFullyExpanded,
+            ) { transitionedToGlanceableHub, isIdleOnCommunal, isAnyFullyExpanded ->
+                transitionedToGlanceableHub && isIdleOnCommunal && !isAnyFullyExpanded
+            }
+            .distinctUntilChanged()
+
     private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
     val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
index 840c3a8..0fe9bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -22,8 +22,10 @@
 import android.graphics.Rect
 import android.view.View
 import android.view.ViewOutlineProvider
+import android.view.accessibility.AccessibilityNodeInfo
 import com.android.systemui.animation.LaunchableView
 import com.android.systemui.animation.LaunchableViewDelegate
+import com.android.systemui.res.R
 
 /** AppWidgetHostView that displays in communal hub with support for rounded corners. */
 class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView {
@@ -42,6 +44,25 @@
     init {
         enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context)
         enforcedRectangle = Rect()
+
+        accessibilityDelegate =
+            object : AccessibilityDelegate() {
+                override fun onInitializeAccessibilityNodeInfo(
+                    host: View,
+                    info: AccessibilityNodeInfo
+                ) {
+                    super.onInitializeAccessibilityNodeInfo(host, info)
+                    // Hint user to long press in order to enter edit mode
+                    info.addAction(
+                        AccessibilityNodeInfo.AccessibilityAction(
+                            AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+                            context.getString(
+                                R.string.accessibility_action_label_edit_widgets
+                            ).lowercase()
+                        )
+                    )
+                }
+            }
     }
 
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index d5b05ef..60469c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -116,8 +116,6 @@
 public class InternetDialogController implements AccessPointController.AccessPointCallback {
 
     private static final String TAG = "InternetDialogController";
-    private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
-            "android.settings.NETWORK_PROVIDER_SETTINGS";
     private static final String ACTION_WIFI_SCANNING_SETTINGS =
             "android.settings.WIFI_SCANNING_SETTINGS";
     /**
@@ -361,7 +359,8 @@
 
     @VisibleForTesting
     protected Intent getSettingsIntent() {
-        return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return new Intent(Settings.ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK);
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index 0dcbe9b2..229bdce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -26,8 +26,9 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
 
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.annotations.WeaklyReferencedCallback;
@@ -118,7 +119,7 @@
     private final Intent mServiceIntent;
     private final UserTracker mUserTracker;
     private final int mFlags;
-    private final Executor mExecutor;
+    private final Executor mBgExecutor;
     private final ServiceTransformer<T> mTransformer;
     private final ArrayList<WeakReference<Callback<T>>> mCallbacks;
     private Optional<Integer> mLastDisconnectReason;
@@ -130,30 +131,34 @@
      * Default constructor for {@link ObservableServiceConnection}.
      * @param context       The context from which the service will be bound with.
      * @param serviceIntent The intent to  bind service with.
-     * @param executor      The executor for connection callbacks to be delivered on
+     * @param bgExecutor    The executor for connection callbacks to be delivered on
      * @param transformer   A {@link ServiceTransformer} for transforming the resulting service
      *                      into a desired type.
      */
     @Inject
     public ObservableServiceConnection(Context context, Intent serviceIntent,
             UserTracker userTracker,
-            @Main Executor executor,
+            @Background Executor bgExecutor,
             ServiceTransformer<T> transformer) {
         mContext = context;
         mServiceIntent = serviceIntent;
         mUserTracker = userTracker;
         mFlags = Context.BIND_AUTO_CREATE;
-        mExecutor = executor;
+        mBgExecutor = bgExecutor;
         mTransformer = transformer;
         mCallbacks = new ArrayList<>();
         mLastDisconnectReason = Optional.empty();
     }
 
     /**
-     * Initiate binding to the service.
-     * @return {@code true} if initiating binding succeed, {@code false} otherwise.
+     * Initiate binding to the service in the background.
      */
-    public boolean bind() {
+    public void bind() {
+        mBgExecutor.execute(this::bindInternal);
+    }
+
+    @WorkerThread
+    private void bindInternal() {
         boolean bindResult = false;
         try {
             bindResult = mContext.bindServiceAsUser(mServiceIntent, this, mFlags,
@@ -166,18 +171,17 @@
         if (DEBUG) {
             Log.d(TAG, "bind. bound:" + bindResult);
         }
-        return bindResult;
     }
 
     /**
      * Disconnect from the service if bound.
      */
     public void unbind() {
-        onDisconnected(DISCONNECT_REASON_UNBIND);
+        mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_UNBIND));
     }
 
     /**
-     * Adds a callback for receiving connection updates.
+     * Adds a callback for receiving connection updates. The callback is executed in the background.
      * @param callback The {@link Callback} to receive future updates.
      */
     public void addCallback(Callback<T> callback) {
@@ -185,7 +189,7 @@
             Log.d(TAG, "addCallback:" + callback);
         }
 
-        mExecutor.execute(() -> {
+        mBgExecutor.execute(() -> {
             final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
 
             while (iterator.hasNext()) {
@@ -210,14 +214,15 @@
      * Removes previously added callback from receiving future connection updates.
      * @param callback The {@link Callback} to be removed.
      */
-    public void removeCallback(Callback callback) {
+    public void removeCallback(Callback<T> callback) {
         if (DEBUG) {
             Log.d(TAG, "removeCallback:" + callback);
         }
 
-        mExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
+        mBgExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
     }
 
+    @WorkerThread
     private void onDisconnected(@DisconnectReason int reason) {
         if (DEBUG) {
             Log.d(TAG, "onDisconnected:" + reason);
@@ -240,7 +245,7 @@
 
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
-        mExecutor.execute(() -> {
+        mBgExecutor.execute(() -> {
             if (DEBUG) {
                 Log.d(TAG, "onServiceConnected");
             }
@@ -268,7 +273,7 @@
         final Iterator<WeakReference<Callback<T>>> iterator = mCallbacks.iterator();
 
         while (iterator.hasNext()) {
-            final Callback cb = iterator.next().get();
+            final Callback<T> cb = iterator.next().get();
             if (cb != null) {
                 applicator.accept(cb);
             } else {
@@ -279,16 +284,16 @@
 
     @Override
     public void onServiceDisconnected(ComponentName name) {
-        mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
+        mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_DISCONNECTED));
     }
 
     @Override
     public void onBindingDied(ComponentName name) {
-        mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
+        mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_BINDING_DIED));
     }
 
     @Override
     public void onNullBinding(ComponentName name) {
-        mExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
+        mBgExecutor.execute(() -> onDisconnected(DISCONNECT_REASON_NULL_BINDING));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 5979f3e..64f8246 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -48,7 +48,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final SystemClock mSystemClock;
-    private final DelayableExecutor mMainExecutor;
+    private final DelayableExecutor mBgExecutor;
     private final int mBaseReconnectDelayMs;
     private final int mMaxReconnectAttempts;
     private final int mMinConnectionDuration;
@@ -71,8 +71,8 @@
 
     private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
 
-    private final ObservableServiceConnection.Callback mConnectionCallback =
-            new ObservableServiceConnection.Callback() {
+    private final ObservableServiceConnection.Callback<T> mConnectionCallback =
+            new ObservableServiceConnection.Callback<>() {
         private long mStartTime;
 
         @Override
@@ -95,12 +95,10 @@
         }
     };
 
-    // TODO: b/326449074 - Ensure the DelayableExecutor is on the correct thread, and update the
-    //                     qualifier (to @Main) or name (to bgExecutor) to be consistent with that.
     @Inject
     public PersistentConnectionManager(
             SystemClock clock,
-            @Background DelayableExecutor mainExecutor,
+            @Background DelayableExecutor bgExecutor,
             DumpManager dumpManager,
             @Named(DUMPSYS_NAME) String dumpsysName,
             @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
@@ -109,7 +107,7 @@
             @Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
             @Named(OBSERVER) Observer observer) {
         mSystemClock = clock;
-        mMainExecutor = mainExecutor;
+        mBgExecutor = bgExecutor;
         mConnection = serviceConnection;
         mObserver = observer;
         mDumpManager = dumpManager;
@@ -195,7 +193,7 @@
                     "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
         }
 
-        mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+        mCurrentReconnectCancelable = mBgExecutor.executeDelayed(mConnectRunnable,
                 reconnectDelayMs);
 
         mReconnectAttempts++;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index ee642a6..0386338 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.media.AudioManager
+import android.util.Log
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
@@ -144,6 +145,7 @@
             if (isMutedOrNoVolume) {
                 when (audioStream.value) {
                     AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+                    AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off
                     AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
                     AudioManager.STREAM_RING ->
                         if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
@@ -158,12 +160,18 @@
                             R.drawable.ic_volume_off
                         }
                     AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
-                    else -> null
+                    else -> {
+                        Log.wtf(TAG, "No icon for the stream: $audioStream")
+                        R.drawable.ic_volume_off
+                    }
                 }
             } else {
                 iconsByStream[audioStream]
+                    ?: run {
+                        Log.wtf(TAG, "No icon for the stream: $audioStream")
+                        R.drawable.ic_music_note
+                    }
             }
-                ?: error("No icon for the stream: $audioStream")
         return Icon.Resource(iconRes, null)
     }
 
@@ -196,4 +204,8 @@
      * when using [AudioStream] directly because it expects another type.
      */
     class FactoryAudioStreamWrapper(val audioStream: AudioStream)
+
+    private companion object {
+        const val TAG = "AudioStreamSliderViewModel"
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 5d34120..8d26c87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.util.service;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -118,6 +116,7 @@
         connection.addCallback(mCallback);
         mExecutor.runAllReady();
         connection.bind();
+        mExecutor.runAllReady();
 
         when(mTransformer.convert(eq(mBinder))).thenReturn(mResult);
 
@@ -143,8 +142,8 @@
         when(mContext.bindServiceAsUser(eq(mIntent), eq(connection), anyInt(),
                 eq(UserHandle.of(MAIN_USER_ID)))).thenReturn(true);
         connection.bind();
+        mExecutor.runAllReady();
         connection.onServiceDisconnected(mComponentName);
-
         mExecutor.runAllReady();
 
         // Ensure proper disconnect reason reported back
@@ -157,6 +156,7 @@
         clearInvocations(mContext);
         // Ensure unbind after disconnect has no effect on the connection
         connection.unbind();
+        mExecutor.runAllReady();
         verify(mContext, never()).unbindService(eq(connection));
     }
 
@@ -197,7 +197,8 @@
 
         // Verify that the exception was caught and that bind returns false, and we properly
         // unbind.
-        assertThat(connection.bind()).isFalse();
+        connection.bind();
+        mExecutor.runAllReady();
         verify(mContext).unbindService(connection);
     }
 
@@ -212,13 +213,15 @@
                 .thenThrow(new SecurityException());
 
         // Verify that bind returns false and we properly unbind.
-        assertThat(connection.bind()).isFalse();
+        connection.bind();
+        mExecutor.runAllReady();
         verify(mContext).unbindService(connection);
 
         clearInvocations(mContext);
 
         // Ensure unbind after the failed bind has no effect.
         connection.unbind();
+        mExecutor.runAllReady();
         verify(mContext, never()).unbindService(eq(connection));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 2017954..56e5e29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,10 +77,10 @@
 import android.os.UserManager;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.dreams.IDreamManager;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -99,46 +99,28 @@
 import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.data.repository.SceneContainerRepository;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.logger.SceneLogger;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.LargeScreenHeaderHelper;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeWindowLogger;
-import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
-import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.NotificationEntryHelper;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -154,7 +136,6 @@
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProviderTestUtil;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -163,13 +144,10 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
-import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
-import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
 import com.android.systemui.util.FakeEventLog;
 import com.android.systemui.util.settings.FakeGlobalSettings;
 import com.android.systemui.util.settings.SystemSettings;
@@ -207,8 +185,6 @@
 import com.android.wm.shell.taskview.TaskViewTransitions;
 import com.android.wm.shell.transition.Transitions;
 
-import kotlinx.coroutines.test.TestScope;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -227,8 +203,11 @@
 import java.util.Optional;
 import java.util.concurrent.Executor;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class BubblesTest extends SysuiTestCase {
     @Mock
@@ -355,11 +334,8 @@
     private Icon mAppBubbleIcon;
     @Mock
     private Display mDefaultDisplay;
-    @Mock
-    private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
 
     private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-    private final TestScope mTestScope = mKosmos.getTestScope();
     private ShadeInteractor mShadeInteractor;
     private ShellTaskOrganizer mShellTaskOrganizer;
     private TaskViewTransitions mTaskViewTransitions;
@@ -376,8 +352,16 @@
     private UserHandle mUser0;
 
     private FakeBubbleProperties mBubbleProperties;
-    private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor;
-    private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor;
+
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+    }
+
+    public BubblesTest(FlagsParameterization flags) {
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
 
     @Before
     public void setUp() throws Exception {
@@ -402,77 +386,14 @@
 
 
         FakeDeviceProvisioningRepository deviceProvisioningRepository =
-                new FakeDeviceProvisioningRepository();
+                mKosmos.getFakeDeviceProvisioningRepository();
         deviceProvisioningRepository.setDeviceProvisioned(true);
-        FakeKeyguardRepository keyguardRepository = new FakeKeyguardRepository();
-        FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
-        FakeShadeRepository shadeRepository = new FakeShadeRepository();
-        FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
-
-        PowerInteractor powerInteractor = new PowerInteractor(
-                mKosmos.getPowerRepository(),
-                mKosmos.getFalsingCollector(),
-                mock(ScreenOffAnimationController.class),
-                mStatusBarStateController);
-
-        SceneInteractor sceneInteractor = new SceneInteractor(
-                mTestScope.getBackgroundScope(),
-                new SceneContainerRepository(
-                        mTestScope.getBackgroundScope(),
-                        mKosmos.getFakeSceneContainerConfig(),
-                        mKosmos.getSceneDataSource()),
-                mock(SceneLogger.class),
-                mKosmos.getDeviceUnlockedInteractor());
-
-        KeyguardTransitionInteractor keyguardTransitionInteractor =
-                mKosmos.getKeyguardTransitionInteractor();
-        KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
-                keyguardRepository,
-                new FakeCommandQueue(),
-                powerInteractor,
-                new FakeKeyguardBouncerRepository(),
-                new ConfigurationInteractor(configurationRepository),
-                shadeRepository,
-                keyguardTransitionInteractor,
-                () -> sceneInteractor,
-                () -> mKosmos.getFromGoneTransitionInteractor(),
-                () -> mKosmos.getSharedNotificationContainerInteractor(),
-                mTestScope);
-
-        mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
-        mFromPrimaryBouncerTransitionInteractor =
-                mKosmos.getFromPrimaryBouncerTransitionInteractor();
-
-        ResourcesSplitShadeStateController splitShadeStateController =
-                new ResourcesSplitShadeStateController();
 
         DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
                 mock(DeviceEntryUdfpsInteractor.class);
         when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
 
-        mShadeInteractor =
-                new ShadeInteractorImpl(
-                        mTestScope.getBackgroundScope(),
-                        mKosmos.getDeviceProvisioningInteractor(),
-                        new FakeDisableFlagsRepository(),
-                        mDozeParameters,
-                        keyguardRepository,
-                        keyguardTransitionInteractor,
-                        powerInteractor,
-                        new FakeUserSetupRepository(),
-                        mock(UserSwitcherInteractor.class),
-                        new ShadeInteractorLegacyImpl(
-                                mTestScope.getBackgroundScope(), keyguardRepository,
-                                new SharedNotificationContainerInteractor(
-                                        configurationRepository,
-                                        mContext,
-                                        splitShadeStateController,
-                                        keyguardInteractor,
-                                        deviceEntryUdfpsInteractor,
-                                        () -> mLargeScreenHeaderHelper),
-                                shadeRepository
-                        )
-                );
+        mShadeInteractor = mKosmos.getShadeInteractor();
 
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
                 mContext,
@@ -2467,6 +2388,10 @@
             mStateChangeCalls++;
             mLastUpdate = update;
         }
+
+        @Override
+        public void animateBubbleBarLocation(BubbleBarLocation location) {
+        }
     }
 
     private static class FakeBubbleProperties implements BubbleProperties {
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index e77f846f..f6885e1 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -1,11 +1,18 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
 {
   "presubmit": [
+    { "name": "tiny-framework-dump-test" },
+    { "name": "hoststubgentest" },
+    { "name": "hoststubgen-invoke-test" },
     {
       "name": "RavenwoodMockitoTest_device"
     },
     {
       "name": "RavenwoodBivalentTest_device"
     },
+    // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
     {
       "name": "SystemUIGoogleTests",
       "options": [
@@ -18,6 +25,19 @@
       ]
     }
   ],
+  "presubmit-large": [
+    {
+      "name": "SystemUITests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ],
   "ravenwood-presubmit": [
     {
       "name": "RavenwoodMinimumTest",
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index b5843d0..beacde2 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -39,14 +39,18 @@
     local jar=$1
     local file=$2
 
-    sed -e '1d' -e "s/^/$jar,/"  $file
+    # Use sed to remove the header + prepend the jar filename.
+    sed -e '1d' -e "s/^/$jar,/" $file
 }
 
 collect_stats() {
     local out="$1"
     {
-        echo 'Jar,PackageName,ClassName,SupportedMethods,TotalMethods'
-        dump "framework-minus-apex"  hoststubgen_framework-minus-apex_stats.csv
+        # Copy the header, with the first column appended.
+        echo -n "Jar,"
+        head -n 1 hoststubgen_framework-minus-apex_stats.csv
+
+        dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv
         dump "service.core"  hoststubgen_services.core_stats.csv
     } > "$out"
 
@@ -56,7 +60,10 @@
 collect_apis() {
     local out="$1"
     {
-        echo 'Jar,PackageName,ClassName,MethodName,Descriptor'
+        # Copy the header, with the first column appended.
+        echo -n "Jar,"
+        head -n 1 hoststubgen_framework-minus-apex_apis.csv
+
         dump "framework-minus-apex"  hoststubgen_framework-minus-apex_apis.csv
         dump "service.core"  hoststubgen_services.core_apis.csv
     } > "$out"
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 23891d2..ec0d897 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3864,10 +3864,12 @@
             final long lastTopTime = sr.app.mState.getLastTopTime();
             final long constantTimeLimit = getTimeLimitForFgsType(fgsType);
             final long nowUptime = SystemClock.uptimeMillis();
-            if (constantTimeLimit > (nowUptime - lastTopTime)) {
+            if (lastTopTime != Long.MIN_VALUE && constantTimeLimit > (nowUptime - lastTopTime)) {
+                // Discard any other messages for this service
+                mFGSAnrTimer.discard(sr);
+                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
                 // The app was in the TOP state after the FGS was started so its time allowance
                 // should be counted from that time since this is considered a user interaction
-                mFGSAnrTimer.discard(sr);
                 final Message msg = mAm.mHandler.obtainMessage(
                                         ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
                 mAm.mHandler.sendMessageAtTime(msg, lastTopTime + constantTimeLimit);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 77654d4..da528a2 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1772,6 +1772,7 @@
 
         @Override
         public void handleMessage(Message msg) {
+            int muteCheckDelayMs = BTA2DP_MUTE_CHECK_DELAY_MS;
             switch (msg.what) {
                 case MSG_RESTORE_DEVICES:
                     synchronized (mSetModeLock) {
@@ -1870,7 +1871,7 @@
                             btInfo.mDevice, btInfo.mProfile, btInfo.mIsLeOutput,
                             "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
                     synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
+                        muteCheckDelayMs += mDeviceInventory.onBluetoothDeviceConfigChange(btInfo,
                                 codecAndChanged.first, codecAndChanged.second,
                                 BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
                     }
@@ -2060,7 +2061,7 @@
             // Give some time to Bluetooth service to post a connection message
             // in case of active device switch
             if (MESSAGES_MUTE_MUSIC.contains(msg.what)) {
-                sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, BTA2DP_MUTE_CHECK_DELAY_MS);
+                sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, muteCheckDelayMs);
             }
 
             if (isMessageHandledUnderWakelock(msg.what)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 9bdc51e..c9612ca 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -864,9 +864,25 @@
         }
     }
 
+    // Additional delay added to the music mute duration when a codec config change is executed.
+    static final int BT_CONFIG_CHANGE_MUTE_DELAY_MS = 500;
 
+    /**
+     * Handles a Bluetooth link codec configuration change communicated by the Bluetooth stack.
+     * Called when either A2DP or LE Audio codec encoding or sampling rate changes:
+     * the change is communicated to native audio policy to eventually reconfigure the audio
+     * path.
+     * Also used to notify a change in preferred mode (duplex or output) for Bluetooth profiles.
+     *
+     * @param btInfo contains all information on the Bluetooth device and profile
+     * @param codec the requested audio encoding (e.g SBC)
+     * @param codecChanged true if a codec parameter changed, false for preferred mode change
+     * @param event currently only EVENT_DEVICE_CONFIG_CHANGE
+     * @return an optional additional delay in milliseconds to add to the music mute period in
+     * case of an actual codec reconfiguration.
+     */
     @GuardedBy("mDeviceBroker.mDeviceStateLock")
-    /*package*/ void onBluetoothDeviceConfigChange(
+    /*package*/ int onBluetoothDeviceConfigChange(
             @NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
             @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
             boolean codecChanged, int event) {
@@ -874,10 +890,11 @@
                 + "onBluetoothDeviceConfigChange")
                 .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
 
+        int delayMs = 0;
         final BluetoothDevice btDevice = btInfo.mDevice;
         if (btDevice == null) {
             mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
-            return;
+            return delayMs;
         }
         if (AudioService.DEBUG_DEVICES) {
             Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
@@ -899,7 +916,7 @@
                         .printSlog(EventLogger.Event.ALOGI, TAG));
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
                         .record();
-                return;
+                return delayMs;
             }
             final String key = DeviceInfo.makeDeviceListKey(
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -907,7 +924,7 @@
             if (di == null) {
                 Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
-                return;
+                return delayMs;
             }
 
             mmi.set(MediaMetrics.Property.ADDRESS, address)
@@ -915,7 +932,6 @@
                     .set(MediaMetrics.Property.INDEX, volume)
                     .set(MediaMetrics.Property.NAME, di.mDeviceName);
 
-
             if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
                 if (btInfo.mProfile == BluetoothProfile.A2DP
                         || btInfo.mProfile == BluetoothProfile.LE_AUDIO
@@ -943,6 +959,7 @@
                                             + address
                                             + " codec=" + AudioSystem.audioFormatToString(codec))
                                     .printSlog(EventLogger.Event.ALOGI, TAG));
+                            delayMs = BT_CONFIG_CHANGE_MUTE_DELAY_MS;
                         }
                     }
                 }
@@ -952,6 +969,7 @@
             }
         }
         mmi.record();
+        return delayMs;
     }
 
     /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index d669c8d..030ce12 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -61,6 +61,8 @@
                 return getSoundDoseValue();
             case "reset-sound-dose-timeout":
                 return resetSoundDoseTimeout();
+            case "set-ringer-mode":
+                return setRingerMode();
             case "set-volume":
                 return setVolume();
             case "set-device-volume":
@@ -100,6 +102,8 @@
         pw.println("    Returns the current sound dose value");
         pw.println("  reset-sound-dose-timeout");
         pw.println("    Resets the sound dose timeout used for momentary exposure");
+        pw.println("  set-ringer-mode NORMAL|SILENT|VIBRATE");
+        pw.println("    Sets the Ringer mode to one of NORMAL|SILENT|VIBRATE");
         pw.println("  set-volume STREAM_TYPE VOLUME_INDEX");
         pw.println("    Sets the volume for STREAM_TYPE to VOLUME_INDEX");
         pw.println("  set-device-volume STREAM_TYPE VOLUME_INDEX NATIVE_DEVICE_TYPE");
@@ -150,6 +154,34 @@
         return 0;
     }
 
+    private int setRingerMode() {
+        String ringerModeText = getNextArg();
+        if (ringerModeText == null) {
+            getErrPrintWriter().println("Error: no ringer mode specified");
+            return 1;
+        }
+
+        final int ringerMode = getRingerMode(ringerModeText);
+        if (!AudioManager.isValidRingerMode(ringerMode)) {
+            getErrPrintWriter().println(
+                    "Error: invalid value of ringerMode, should be one of NORMAL|SILENT|VIBRATE");
+            return 1;
+        }
+
+        final AudioManager am = mService.mContext.getSystemService(AudioManager.class);
+        am.setRingerModeInternal(ringerMode);
+        return 0;
+    }
+
+    private int getRingerMode(String ringerModeText) {
+        return switch (ringerModeText) {
+            case "NORMAL" -> AudioManager.RINGER_MODE_NORMAL;
+            case "VIBRATE" -> AudioManager.RINGER_MODE_VIBRATE;
+            case "SILENT" -> AudioManager.RINGER_MODE_SILENT;
+            default -> -1;
+        };
+    }
+
     private int getIsSurroundFormatEnabled() {
         String surroundFormatText = getNextArg();
 
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 4fc1a17..ad6b0ca 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -1,7 +1,7 @@
 {
   "presubmit-large": [
     {
-      "name": "CtsHostsideNetworkTests",
+      "name": "CtsHostsideNetworkPolicyTests",
       "options": [
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 4747689..143bc5c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1725,7 +1725,28 @@
         synchronized (mConfigLock) {
             if (policy == null || mConfig == null) return;
             final ZenModeConfig newConfig = mConfig.copy();
-            newConfig.applyNotificationPolicy(policy);
+            if (Flags.modesApi() && !Flags.modesUi()) {
+                // Fix for b/337193321 -- propagate changes to notificationPolicy to rules where
+                // the user cannot edit zen policy to emulate the previous "inheritance".
+                ZenPolicy previousPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+                        newConfig.toNotificationPolicy());
+                ZenPolicy newPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+
+                newConfig.applyNotificationPolicy(policy);
+
+                if (!previousPolicy.equals(newPolicy)) {
+                    for (ZenRule rule : newConfig.automaticRules.values()) {
+                        if (!SystemZenRules.isSystemOwnedRule(rule)
+                                && rule.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+                                && (rule.zenPolicy == null || rule.zenPolicy.equals(previousPolicy)
+                                        || rule.zenPolicy.equals(getDefaultZenPolicy()))) {
+                            rule.zenPolicy = newPolicy;
+                        }
+                    }
+                }
+            } else {
+                newConfig.applyNotificationPolicy(policy);
+            }
             setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid);
         }
     }
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index c8fd7e4..8a85328 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.AppOpsManager;
@@ -68,6 +69,7 @@
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.OptionalInt;
@@ -335,14 +337,22 @@
     }
 
     static class Injector {
+        class RoleManagerWrapper {
+            List<String> getRoleHolders(@NonNull String roleName) {
+                return mContext.getSystemService(RoleManager.class).getRoleHolders(roleName);
+            }
+        }
+
         Context mContext;
         ArraySet<String> mAllowlistedPackages;
         AtomicFile mMappingFile;
+        RoleManagerWrapper mRoleManagerWrapper;
 
         Injector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile) {
             mContext = context;
             mAllowlistedPackages = allowlistedPackages;
             mMappingFile = mappingFile;
+            mRoleManagerWrapper = new RoleManagerWrapper();
         }
 
         Context getContext() {
@@ -368,6 +378,10 @@
         void setSystemProperty(String key, String value) {
             SystemProperties.set(key, value);
         }
+
+        RoleManagerWrapper getRoleManagerWrapper() {
+            return mRoleManagerWrapper;
+        }
     }
 
     BugreportManagerServiceImpl(Context context) {
@@ -546,7 +560,7 @@
         if (!allowlisted) {
             final long token = Binder.clearCallingIdentity();
             try {
-                allowlisted = mContext.getSystemService(RoleManager.class).getRoleHolders(
+                allowlisted = mInjector.getRoleManagerWrapper().getRoleHolders(
                         ROLE_SYSTEM_AUTOMOTIVE_PROJECTION).contains(callingPackage);
             } finally {
                 Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0f4e482..ae485ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3984,6 +3984,8 @@
 
         // packageName -> list of components to send broadcasts now
         final ArrayMap<String, ArrayList<String>> sendNowBroadcasts = new ArrayMap<>(targetSize);
+        final List<PackageMetrics.ComponentStateMetrics> componentStateMetricsList =
+                new ArrayList<PackageMetrics.ComponentStateMetrics>();
         synchronized (mLock) {
             Computer computer = snapshotComputer();
             boolean scheduleBroadcastMessage = false;
@@ -3997,11 +3999,17 @@
                 // update enabled settings
                 final ComponentEnabledSetting setting = settings.get(i);
                 final String packageName = setting.getPackageName();
-                if (!setEnabledSettingInternalLocked(computer, pkgSettings.get(packageName),
-                        setting, userId, callingPackage)) {
+                final PackageSetting packageSetting = pkgSettings.get(packageName);
+                final PackageMetrics.ComponentStateMetrics componentStateMetrics =
+                        new PackageMetrics.ComponentStateMetrics(setting,
+                                UserHandle.getUid(userId, packageSetting.getAppId()),
+                                packageSetting.getEnabled(userId));
+                if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
+                        callingPackage)) {
                     continue;
                 }
                 anyChanged = true;
+                componentStateMetricsList.add(componentStateMetrics);
 
                 if ((setting.getEnabledFlags() & PackageManager.SYNCHRONOUS) != 0) {
                     isSynchronous = true;
@@ -4029,6 +4037,9 @@
                 return;
             }
 
+            // Log the metrics when the component state is changed.
+            PackageMetrics.reportComponentStateChanged(computer, componentStateMetricsList, userId);
+
             if (isSynchronous) {
                 flushPackageRestrictionsAsUserInternalLocked(userId);
             } else {
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index a0b6897..20598f9 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,12 +19,21 @@
 import static android.os.Process.INVALID_UID;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.admin.SecurityLog;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.util.FrameworkStatsLog;
@@ -41,12 +50,14 @@
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * Metrics class for reporting stats to logging infrastructures like statsd
  */
 final class PackageMetrics {
+    private static final String TAG = "PackageMetrics";
     public static final int STEP_PREPARE = 1;
     public static final int STEP_SCAN = 2;
     public static final int STEP_RECONCILE = 3;
@@ -344,4 +355,76 @@
         SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UNINSTALLED, packageName, versionCode,
                 userId);
     }
+
+    public static class ComponentStateMetrics {
+        public int mUid;
+        public int mComponentOldState;
+        public int mComponentNewState;
+        public boolean mIsForWholeApp;
+        @NonNull private String mPackageName;
+        @Nullable private String mClassName;
+
+        ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid,
+                int componentOldState) {
+            mUid = uid;
+            mComponentOldState = componentOldState;
+            mComponentNewState = setting.getEnabledState();
+            mIsForWholeApp = !setting.isComponent();
+            mPackageName = setting.getPackageName();
+            mClassName = setting.getClassName();
+        }
+
+        public boolean isSameComponent(ActivityInfo activityInfo) {
+            if (activityInfo == null) {
+                return false;
+            }
+            return mIsForWholeApp ? TextUtils.equals(activityInfo.packageName, mPackageName)
+                    : activityInfo.getComponentName().equals(
+                            new ComponentName(mPackageName, mClassName));
+        }
+    }
+
+    public static void reportComponentStateChanged(@NonNull Computer computer,
+            List<ComponentStateMetrics> componentStateMetricsList, @UserIdInt int userId) {
+        if (!Flags.componentStateChangedMetrics()) {
+            return;
+        }
+        if (componentStateMetricsList == null || componentStateMetricsList.isEmpty()) {
+            Slog.d(TAG, "Fail to report component state due to metrics is empty");
+            return;
+        }
+        boolean isLauncher = false;
+        final List<ResolveInfo> resolveInfosForLauncher = getHomeActivitiesResolveInfoAsUser(
+                computer, userId);
+        final int resolveInfosForLauncherSize =
+                resolveInfosForLauncher != null ? resolveInfosForLauncher.size() : 0;
+        final int metricsSize = componentStateMetricsList.size();
+        for (int i = 0; i < metricsSize; i++) {
+            final ComponentStateMetrics componentStateMetrics = componentStateMetricsList.get(i);
+            for (int j = 0; j < resolveInfosForLauncherSize; j++) {
+                ResolveInfo resolveInfo = resolveInfosForLauncher.get(j);
+                if (componentStateMetrics.isSameComponent(resolveInfo.activityInfo)) {
+                    isLauncher = true;
+                    break;
+                }
+            }
+            reportComponentStateChanged(componentStateMetrics.mUid,
+                    componentStateMetrics.mComponentOldState,
+                    componentStateMetrics.mComponentNewState,
+                    isLauncher,
+                    componentStateMetrics.mIsForWholeApp);
+        }
+    }
+
+    private static void reportComponentStateChanged(int uid, int componentOldState,
+            int componentNewState, boolean isLauncher, boolean isForWholeApp) {
+        FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
+                uid, componentOldState, componentNewState, isLauncher, isForWholeApp);
+    }
+
+    private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
+            @UserIdInt int userId) {
+        return computer.queryIntentActivitiesInternal(computer.getHomeIntent(), /* resolvedType */
+                null, /* flags */ 0, userId);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ff75736..d555f1a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5673,18 +5673,15 @@
 
     private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token,
             int flags, CallerIdentity caller) {
-        final boolean isPin = PasswordMetrics.isNumericOnly(password);
-        try (LockscreenCredential newCredential =
-                isPin ? LockscreenCredential.createPin(password) :
-                       LockscreenCredential.createPasswordOrNone(password)) {
-            return resetPasswordInternal(newCredential, tokenHandle, token, flags, caller);
-        }
-    }
-
-    private boolean resetPasswordInternal(LockscreenCredential newCredential,
-            long tokenHandle, byte[] token, int flags, CallerIdentity caller) {
         final int callingUid = caller.getUid();
         final int userHandle = UserHandle.getUserId(callingUid);
+        final boolean isPin = PasswordMetrics.isNumericOnly(password);
+        final LockscreenCredential newCredential;
+        if (isPin) {
+            newCredential = LockscreenCredential.createPin(password);
+        } else {
+            newCredential = LockscreenCredential.createPasswordOrNone(password);
+        }
         synchronized (getLockObject()) {
             final PasswordMetrics minMetrics = getPasswordMinimumMetricsUnchecked(userHandle);
             final int complexity = getAggregatedPasswordComplexityLocked(userHandle);
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index 23314cd..1322545 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -20,12 +20,17 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
 import android.os.Looper;
 import android.platform.test.annotations.EnableFlags;
 import android.service.dreams.DreamService;
@@ -41,6 +46,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -83,6 +89,18 @@
         assertThat(metadata.dreamCategory).isEqualTo(DreamService.DREAM_CATEGORY_DEFAULT);
     }
 
+    @Test
+    public void testMetadataParsing_exceptionReading() {
+        final PackageManager packageManager = Mockito.mock(PackageManager.class);
+        final ServiceInfo serviceInfo = Mockito.mock(ServiceInfo.class);
+        final TypedArray rawMetadata = Mockito.mock(TypedArray.class);
+        when(packageManager.extractPackageItemInfoAttributes(eq(serviceInfo), any(), any(), any()))
+                .thenReturn(rawMetadata);
+        when(rawMetadata.getString(anyInt())).thenThrow(new RuntimeException("failure"));
+
+        assertThat(DreamService.getDreamMetadata(packageManager, serviceInfo)).isNull();
+    }
+
     private DreamService.DreamMetadata getDreamMetadata(String dreamClassName)
             throws PackageManager.NameNotFoundException {
         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index a6f2196..9862663 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.os;
 
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
@@ -25,9 +23,9 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.flags.Flags;
-import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -61,6 +59,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.FileDescriptor;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -104,7 +104,7 @@
         ArraySet<String> mAllowlistedPackages = new ArraySet<>();
         mAllowlistedPackages.add(mContext.getPackageName());
         mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
-                mMockUserManager, mMockDevicePolicyManager);
+                mMockUserManager, mMockDevicePolicyManager, null);
         mService = new BugreportManagerServiceImpl(mInjector);
         mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
         when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
@@ -114,24 +114,8 @@
 
     @After
     public void tearDown() throws Exception {
-        // Changes to RoleManager persist between tests, so we need to clear out any funny
-        // business we did in previous tests.
+        // Clean up the mapping file between tests since it would otherwise persist.
         mMappingFile.delete();
-        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
-        CallbackFuture future = new CallbackFuture();
-        runWithShellPermissionIdentity(
-                () -> {
-                    roleManager.setBypassingRoleQualification(false);
-                    roleManager.removeRoleHolderAsUser(
-                            "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
-                            mContext.getPackageName(),
-                            /* flags= */ 0,
-                            Process.myUserHandle(),
-                            mContext.getMainExecutor(),
-                            future);
-                });
-
-        assertThat(future.get()).isEqualTo(true);
     }
 
     @Test
@@ -267,7 +251,10 @@
 
     @Test
     public void testCancelBugreportWithoutRole() {
-        clearAllowlist();
+        // Create a new service to clear the allowlist
+        mService = new BugreportManagerServiceImpl(
+                new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+                        mMockUserManager, mMockDevicePolicyManager, null));
 
         assertThrows(SecurityException.class, () -> mService.cancelBugreport(
                 Binder.getCallingUid(), mContext.getPackageName()));
@@ -275,29 +262,13 @@
 
     @Test
     public void testCancelBugreportWithRole() throws Exception {
-        clearAllowlist();
-        RoleManager roleManager = mContext.getSystemService(RoleManager.class);
-        CallbackFuture future = new CallbackFuture();
-        runWithShellPermissionIdentity(
-                () -> {
-                    roleManager.setBypassingRoleQualification(true);
-                    roleManager.addRoleHolderAsUser(
-                            "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
-                            mContext.getPackageName(),
-                            /* flags= */ 0,
-                            Process.myUserHandle(),
-                            mContext.getMainExecutor(),
-                            future);
-                });
-
-        assertThat(future.get()).isEqualTo(true);
-        mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
-    }
-
-    private void clearAllowlist() {
+        // Create a new service to clear the allowlist, but override the role manager
         mService = new BugreportManagerServiceImpl(
                 new TestInjector(mContext, new ArraySet<>(), mMappingFile,
-                        mMockUserManager, mMockDevicePolicyManager));
+                        mMockUserManager, mMockDevicePolicyManager,
+                        "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"));
+
+        mService.cancelBugreport(Binder.getCallingUid(), mContext.getPackageName());
     }
 
     private static class Listener implements IDumpstateListener {
@@ -359,10 +330,22 @@
         private boolean mBugreportStarted = false;
 
         TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
-                UserManager um, DevicePolicyManager dpm) {
+                UserManager um, DevicePolicyManager dpm, String grantedRole) {
             super(context, allowlistedPackages, mappingFile);
             mUserManager = um;
             mDevicePolicyManager = dpm;
+
+            if (grantedRole != null) {
+                mRoleManagerWrapper =
+                        new BugreportManagerServiceImpl.Injector.RoleManagerWrapper() {
+                            @Override
+                            List<String> getRoleHolders(@NonNull String roleName) {
+                                return roleName.equals(grantedRole)
+                                        ? Collections.singletonList(mContext.getPackageName())
+                                        : Collections.emptyList();
+                            }
+                        };
+            }
         }
 
         @Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 9559a25..5fdb396 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -6095,6 +6095,67 @@
         assertThat(readPolicy.allowConversations()).isFalse();
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    public void setNotificationPolicy_updatesRulePolicies_ifRulePolicyIsDefaultOrGlobalPolicy() {
+        ZenPolicy defaultZenPolicy = mZenModeHelper.getDefaultZenPolicy();
+        Policy previousManualPolicy = mZenModeHelper.mConfig.toNotificationPolicy();
+        ZenPolicy previousManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(
+                previousManualPolicy);
+        ZenPolicy customZenPolicy = new ZenPolicy.Builder(defaultZenPolicy).allowConversations(
+                CONVERSATION_SENDERS_ANYONE).build();
+
+        mZenModeHelper.mConfig.automaticRules.clear();
+        addZenRule(mZenModeHelper.mConfig, "appWithDefault", "app.pkg",
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+        addZenRule(mZenModeHelper.mConfig, "appWithSameAsManual", "app.pkg",
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+        addZenRule(mZenModeHelper.mConfig, "appWithCustom", "app.pkg",
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS, customZenPolicy);
+        addZenRule(mZenModeHelper.mConfig, "appWithOtherFilter", "app.pkg",
+                ZEN_MODE_ALARMS, null);
+        addZenRule(mZenModeHelper.mConfig, "systemWithDefault", "android",
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS, defaultZenPolicy);
+        addZenRule(mZenModeHelper.mConfig, "systemWithSameAsManual", "android",
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS, previousManualZenPolicy);
+
+        Policy newManualPolicy = new Policy(PRIORITY_CATEGORY_EVENTS, 0, 0);
+        mZenModeHelper.setNotificationPolicy(newManualPolicy, UPDATE_ORIGIN_USER, 0);
+        ZenPolicy newManualZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(newManualPolicy);
+
+        // Only app rules with default or same-as-manual policies were updated.
+        assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithDefault").zenPolicy)
+                .isEqualTo(newManualZenPolicy);
+        assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithSameAsManual").zenPolicy)
+                .isEqualTo(newManualZenPolicy);
+
+        assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithCustom").zenPolicy)
+                .isEqualTo(customZenPolicy);
+        assertThat(mZenModeHelper.mConfig.automaticRules.get("appWithOtherFilter").zenPolicy)
+                .isNull();
+        assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithDefault").zenPolicy)
+                .isEqualTo(defaultZenPolicy);
+        assertThat(mZenModeHelper.mConfig.automaticRules.get("systemWithSameAsManual").zenPolicy)
+                .isEqualTo(previousManualZenPolicy);
+    }
+
+    private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
+            @Nullable ZenPolicy zenPolicy) {
+        ZenRule rule = new ZenRule();
+        rule.id = id;
+        rule.pkg = ownerPkg;
+        rule.enabled = true;
+        rule.zenMode = zenMode;
+        rule.zenPolicy = zenPolicy;
+        // Plus stuff so that isValidAutomaticRule() passes
+        rule.name = String.format("Rule %s from %s with mode=%s and policy=%s", id, ownerPkg,
+                zenMode, zenPolicy);
+        rule.conditionId = Uri.parse(rule.name);
+
+        config.automaticRules.put(id, rule);
+    }
+
     private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
             Correspondence.transforming(zr -> {
                 Parcel p = Parcel.obtain();
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index eca258c..f6885e1 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,13 +1,63 @@
+// Keep the following two TEST_MAPPINGs in sync:
+// frameworks/base/ravenwood/TEST_MAPPING
+// frameworks/base/tools/hoststubgen/TEST_MAPPING
 {
   "presubmit": [
     { "name": "tiny-framework-dump-test" },
     { "name": "hoststubgentest" },
-    { "name": "hoststubgen-invoke-test" }
+    { "name": "hoststubgen-invoke-test" },
+    {
+      "name": "RavenwoodMockitoTest_device"
+    },
+    {
+      "name": "RavenwoodBivalentTest_device"
+    },
+    // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING
+    {
+      "name": "SystemUIGoogleTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ],
+  "presubmit-large": [
+    {
+      "name": "SystemUITests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
   ],
   "ravenwood-presubmit": [
     {
+      "name": "RavenwoodMinimumTest",
+      "host": true
+    },
+    {
+      "name": "RavenwoodMockitoTest",
+      "host": true
+    },
+    {
       "name": "CtsUtilTestCasesRavenwood",
       "host": true
+    },
+    {
+      "name": "RavenwoodCoreTest",
+      "host": true
+    },
+    {
+      "name": "RavenwoodBivalentTest",
+      "host": true
     }
   ]
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 2f432cc..7212beb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -16,6 +16,7 @@
 package com.android.hoststubgen
 
 import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.dumper.ApiDumper
 import com.android.hoststubgen.filters.AnnotationBasedFilter
 import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
 import com.android.hoststubgen.filters.ConstantFilter
@@ -89,7 +90,11 @@
             log.i("Dump file created at $it")
         }
         options.apiListFile.ifSet {
-            PrintWriter(it).use { pw -> stats.dumpApis(pw) }
+            PrintWriter(it).use { pw ->
+                // TODO, when dumping a jar that's not framework-minus-apex.jar, we need to feed
+                // framework-minus-apex.jar so that we can dump inherited methods from it.
+                ApiDumper(pw, allClasses, null, filter).dump()
+            }
             log.i("API list file created at $it")
         }
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
index da61469..9045db2 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
@@ -15,7 +15,8 @@
  */
 package com.android.hoststubgen
 
-import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.asm.getOuterClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
 import com.android.hoststubgen.filters.FilterPolicyWithReason
 import org.objectweb.asm.Opcodes
 import java.io.PrintWriter
@@ -55,8 +56,8 @@
         // Ignore methods where policy isn't relevant
         if (policy.isIgnoredForStats) return
 
-        val packageName = resolvePackageName(fullClassName)
-        val className = resolveOuterClassName(fullClassName)
+        val packageName = getPackageNameFromFullClassName(fullClassName)
+        val className = getOuterClassNameFromFullClassName(fullClassName)
 
         // Ignore methods for certain generated code
         if (className.endsWith("Proto")
@@ -88,42 +89,4 @@
             }
         }
     }
-
-    fun dumpApis(pw: PrintWriter) {
-        pw.printf("PackageName,ClassName,MethodName,MethodDesc\n")
-        apis.sortedWith(compareBy({ it.fullClassName }, { it.methodName }, { it.methodDesc }))
-            .forEach { api ->
-            pw.printf(
-                "%s,%s,%s,%s\n",
-                csvEscape(resolvePackageName(api.fullClassName)),
-                csvEscape(resolveClassName(api.fullClassName)),
-                csvEscape(api.methodName),
-                csvEscape(api.methodDesc),
-                )
-        }
-    }
-
-    private fun resolvePackageName(fullClassName: String): String {
-        val start = fullClassName.lastIndexOf('/')
-        return fullClassName.substring(0, start).toHumanReadableClassName()
-    }
-
-    private fun resolveOuterClassName(fullClassName: String): String {
-        val start = fullClassName.lastIndexOf('/')
-        val end = fullClassName.indexOf('$')
-        if (end == -1) {
-            return fullClassName.substring(start + 1)
-        } else {
-            return fullClassName.substring(start + 1, end)
-        }
-    }
-
-    private fun resolveClassName(fullClassName: String): String {
-        val pos = fullClassName.lastIndexOf('/')
-        if (pos == -1) {
-            return fullClassName
-        } else {
-            return fullClassName.substring(pos + 1)
-        }
-    }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 83e122f..b8d1800 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -76,17 +76,45 @@
     return null
 }
 
-private val removeLastElement = """[./][^./]*$""".toRegex()
+val periodOrSlash = charArrayOf('.', '/')
 
-fun getPackageNameFromClassName(className: String): String {
-    return className.replace(removeLastElement, "")
+fun getPackageNameFromFullClassName(fullClassName: String): String {
+    val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+    if (pos == -1) {
+        return ""
+    } else {
+        return fullClassName.substring(0, pos)
+    }
 }
 
-fun resolveClassName(className: String, packageName: String): String {
+fun getClassNameFromFullClassName(fullClassName: String): String {
+    val pos = fullClassName.lastIndexOfAny(periodOrSlash)
+    if (pos == -1) {
+        return fullClassName
+    } else {
+        return fullClassName.substring(pos + 1)
+    }
+}
+
+fun getOuterClassNameFromFullClassName(fullClassName: String): String {
+    val start = fullClassName.lastIndexOfAny(periodOrSlash)
+    val end = fullClassName.indexOf('$')
+    if (end == -1) {
+        return fullClassName.substring(start + 1)
+    } else {
+        return fullClassName.substring(start + 1, end)
+    }
+}
+
+/**
+ * If [className] is a fully qualified name, just return it.
+ * Otherwise, prepend [defaultPackageName].
+ */
+fun resolveClassNameWithDefaultPackage(className: String, defaultPackageName: String): String {
     if (className.contains('.') || className.contains('/')) {
         return className
     }
-    return "$packageName.$className"
+    return "$defaultPackageName.$className"
 }
 
 fun String.toJvmClassName(): String {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
new file mode 100644
index 0000000..aaefee4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.dumper
+
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.CTOR_NAME
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.getClassNameFromFullClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.csvEscape
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.log
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.ClassNode
+import java.io.PrintWriter
+
+/**
+ * Dump all the API methods in [classes], with inherited methods, with their policies.
+ */
+class ApiDumper(
+    val pw: PrintWriter,
+    val classes: ClassNodes,
+    val frameworkClasses: ClassNodes?,
+    val filter: OutputFilter,
+) {
+    private data class MethodKey(
+        val name: String,
+        val descriptor: String,
+    )
+
+    val javaStandardApiPolicy = FilterPolicy.Stub.withReason("Java standard API")
+
+    private val shownMethods = mutableSetOf<MethodKey>()
+
+    /**
+     * Do the dump.
+     */
+    fun dump() {
+        pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" +
+                ",Supported,Policy,Reason\n")
+
+        classes.forEach { classNode ->
+            shownMethods.clear()
+            dump(classNode, classNode)
+        }
+    }
+
+    private fun dumpMethod(
+        classPackage: String,
+        className: String,
+        isSuperClass: Boolean,
+        methodClassName: String,
+        methodName: String,
+        methodDesc: String,
+        policy: FilterPolicyWithReason,
+    ) {
+        pw.printf(
+            "%s,%s,%d,%s,%s,%s,%d,%s,%s\n",
+            csvEscape(classPackage),
+            csvEscape(className),
+            if (isSuperClass) { 1 } else { 0 },
+            csvEscape(methodClassName),
+            csvEscape(methodName),
+            csvEscape(methodDesc),
+            if (policy.policy.isSupported) { 1 } else { 0 },
+            policy.policy,
+            csvEscape(policy.reason),
+        )
+    }
+
+    private fun isDuplicate(methodName: String, methodDesc: String): Boolean {
+        val methodKey = MethodKey(methodName, methodDesc)
+
+        if (shownMethods.contains(methodKey)) {
+            return true
+        }
+        shownMethods.add(methodKey)
+        return false
+    }
+
+    private fun dump(
+        dumpClass: ClassNode,
+        methodClass: ClassNode,
+        ) {
+        val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+        val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+        val isSuperClass = dumpClass != methodClass
+
+        methodClass.methods?.sortedWith(compareBy({ it.name }, { it.desc }))?.forEach { method ->
+
+            // Don't print ctor's from super classes.
+            if (isSuperClass) {
+                if (CTOR_NAME == method.name || CLASS_INITIALIZER_NAME == method.name) {
+                    return@forEach
+                }
+            }
+            // If we already printed the method from a subclass, don't print it.
+            if (isDuplicate(method.name, method.desc)) {
+                return@forEach
+            }
+
+            val policy = filter.getPolicyForMethod(methodClass.name, method.name, method.desc)
+
+            // Let's skip "Remove" APIs. Ideally we want to print it, just to make the CSV
+            // complete, we still need to hide methods substituted (== @RavenwoodReplace) methods
+            // and for now we don't have an easy way to detect it.
+            if (policy.policy == FilterPolicy.Remove) {
+                return@forEach
+            }
+
+            val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc)
+
+            dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(),
+                renameTo ?: method.name, method.desc, policy)
+       }
+
+        // Dump super class methods.
+        dumpSuper(dumpClass, methodClass.superName)
+
+        // Dump interface methods (which may have default methods).
+        methodClass.interfaces?.sorted()?.forEach { interfaceName ->
+            dumpSuper(dumpClass, interfaceName)
+        }
+    }
+
+    /**
+     * Dump a given super class / interface.
+     */
+    private fun dumpSuper(
+        dumpClass: ClassNode,
+        methodClassName: String,
+    ) {
+        classes.findClass(methodClassName)?.let { methodClass ->
+            dump(dumpClass, methodClass)
+            return
+        }
+        frameworkClasses?.findClass(methodClassName)?.let { methodClass ->
+            dump(dumpClass, methodClass)
+            return
+        }
+        if (methodClassName.startsWith("java/") ||
+            methodClassName.startsWith("javax/")
+            ) {
+            dumpStandardClass(dumpClass, methodClassName)
+            return
+        }
+        log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.")
+    }
+
+    /**
+     * Dump methods from Java standard classes.
+     */
+    private fun dumpStandardClass(
+        dumpClass: ClassNode,
+        methodClassName: String,
+    ) {
+        val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+        val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+
+        val methodClassName = methodClassName.toHumanReadableClassName()
+
+        try {
+            val clazz = Class.forName(methodClassName)
+
+            // Method.getMethods() returns only public methods, but with inherited ones.
+            // Method.getDeclaredMethods() returns private methods too, but no inherited methods.
+            //
+            // Since we're only interested in public ones, just use getMethods().
+            clazz.methods.forEach { method ->
+                val methodName = method.name
+                val methodDesc = Type.getMethodDescriptor(method)
+
+                // If we already printed the method from a subclass, don't print it.
+                if (isDuplicate(methodName, methodDesc)) {
+                    return@forEach
+                }
+
+                dumpMethod(pkg, cls, true, methodClassName,
+                    methodName, methodDesc, javaStandardApiPolicy)
+            }
+        } catch (e: ClassNotFoundException) {
+            log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.")
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 45e140c..6643492 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -20,8 +20,8 @@
 import com.android.hoststubgen.LogLevel
 import com.android.hoststubgen.asm.ClassNodes
 import com.android.hoststubgen.asm.UnifiedVisitor
-import com.android.hoststubgen.asm.getPackageNameFromClassName
-import com.android.hoststubgen.asm.resolveClassName
+import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.resolveClassNameWithDefaultPackage
 import com.android.hoststubgen.asm.toJvmClassName
 import com.android.hoststubgen.filters.FilterPolicy
 import com.android.hoststubgen.filters.FilterPolicyWithReason
@@ -89,7 +89,7 @@
     ) {
         super.visit(version, access, name, signature, superName, interfaces)
         currentClassName = name
-        currentPackageName = getPackageNameFromClassName(name)
+        currentPackageName = getPackageNameFromFullClassName(name)
         classPolicy = filter.getPolicyForClass(currentClassName)
 
         log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName)
@@ -98,7 +98,8 @@
         log.indent()
 
         filter.getNativeSubstitutionClass(currentClassName)?.let { className ->
-            val fullClassName = resolveClassName(className, currentPackageName).toJvmClassName()
+            val fullClassName = resolveClassNameWithDefaultPackage(className, currentPackageName)
+                .toJvmClassName()
             log.d("  NativeSubstitutionClass: $fullClassName")
             if (classes.findClass(fullClassName) == null) {
                 log.w("Native substitution class $fullClassName not found. Class must be " +